Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
de8a877
[WIP] Initial Feature Election algorithm version for Nnidia FLARE
christofilojohn Nov 2, 2025
bb3332f
[WIP] Readme mistake
christofilojohn Nov 2, 2025
345f430
Merge branch 'main' into main
christofilojohn Nov 4, 2025
f7ebada
Merge branch 'main' into main
chesterxgchen Nov 5, 2025
973b818
Merge branch 'main' into main
chesterxgchen Nov 6, 2025
9b53aad
Update examples/advanced/feature_election/requirements.txt
christofilojohn Nov 6, 2025
1144051
Update nvflare/app_opt/feature_election/controller.py
christofilojohn Nov 6, 2025
ad4587a
Update nvflare/app_opt/feature_election/README.md
christofilojohn Nov 6, 2025
56a5c91
Update nvflare/app_opt/feature_election/executor.py
christofilojohn Nov 6, 2025
cc50fbb
[WIP] comments cleanup
christofilojohn Nov 6, 2025
de4e8ff
[WIP] Implemented minor changes on imports and removing PPIMBC_Model.…
christofilojohn Nov 6, 2025
e63ab36
[WIP] Clarification, extra variable k - partition index in controller
christofilojohn Nov 6, 2025
cdf7372
[WIP] Another import restructure
christofilojohn Nov 7, 2025
93de00b
Removed redundant components, added apache licence files
christofilojohn Nov 7, 2025
eff32d3
Update nvflare/app_opt/feature_election/executor.py
christofilojohn Nov 7, 2025
5e15d7b
Merge branch 'main' into main
christofilojohn Nov 8, 2025
10a5c26
Update examples/advanced/feature_election/flare_deployment.py
christofilojohn Nov 8, 2025
1adb62a
Update nvflare/app_opt/feature_election/executor.py
christofilojohn Nov 8, 2025
454551c
Update examples/advanced/feature_election/flare_deployment.py
christofilojohn Nov 8, 2025
49ffa97
Pyimpetus cleanup on executor.py
christofilojohn Nov 8, 2025
53015da
Merge branch 'main' of https://github.com/christofilojohn/NVFlare
christofilojohn Nov 8, 2025
b8453dd
Remove unused imports, per greptile suggestions
christofilojohn Nov 8, 2025
9b534e1
Minor cleanup, following greptile comments
christofilojohn Nov 8, 2025
f736c22
feature election masks now work with both True/False and 1/0 format, …
christofilojohn Nov 8, 2025
5030be8
comment on feature election global mask process
christofilojohn Nov 8, 2025
1418fd6
Skip PyImpetus test if the dependency is not installed
christofilojohn Nov 8, 2025
7df272f
Update nvflare/app_opt/feature_election/executor.py
christofilojohn Nov 10, 2025
f052099
Update nvflare/app_opt/feature_election/controller.py
christofilojohn Nov 10, 2025
e7e4af1
Executor minor cleanup, with fixed imports
christofilojohn Nov 10, 2025
f184c15
Merge branch 'main' of https://github.com/christofilojohn/NVFlare
christofilojohn Nov 10, 2025
aed3826
Update examples/advanced/feature_election/flare_deployment.py
christofilojohn Nov 10, 2025
ac2012f
Update nvflare/app_opt/feature_election/README.md
christofilojohn Nov 10, 2025
ea22b32
moved import to top
christofilojohn Nov 10, 2025
bd0a6d9
Merge branch 'main' of https://github.com/christofilojohn/NVFlare
christofilojohn Nov 10, 2025
e8bc20c
Update examples/advanced/feature_election/flare_deployment.py
christofilojohn Nov 10, 2025
07c2e53
Requirements newline
christofilojohn Nov 10, 2025
b8b292c
Merge branch 'main' of https://github.com/christofilojohn/NVFlare
christofilojohn Nov 10, 2025
3a0580e
fixed path on README
christofilojohn Nov 10, 2025
60c413b
added empty init file to test folder for proper python packaging
christofilojohn Nov 19, 2025
31f0792
Merge branch 'main' into main
christofilojohn Nov 19, 2025
812a1ae
Moved tests inside feature election package
christofilojohn Nov 19, 2025
a0cc193
Merge branch 'main' of https://github.com/christofilojohn/NVFlare
christofilojohn Nov 19, 2025
6223d6f
Update examples/advanced/feature_election/flare_deployment.py
christofilojohn Nov 19, 2025
f93c677
fixed boolean type safety errors, removed redundant installation note…
christofilojohn Nov 19, 2025
6f3de84
Merge branch 'main' of https://github.com/christofilojohn/NVFlare
christofilojohn Nov 19, 2025
449a1ea
Update nvflare/app_opt/feature_election/controller.py
christofilojohn Nov 19, 2025
0dace50
cleanup of imports
christofilojohn Nov 19, 2025
f78857c
Merge branch 'main' of https://github.com/christofilojohn/NVFlare
christofilojohn Nov 19, 2025
9197f4f
Update examples/advanced/feature_election/flare_deployment.py
christofilojohn Nov 19, 2025
3140b3d
removed empty line
christofilojohn Nov 19, 2025
a464527
Merge branch 'main' of https://github.com/christofilojohn/NVFlare
christofilojohn Nov 19, 2025
c2df497
cleanup, removed installation notes which was redundant
christofilojohn Nov 19, 2025
22f2f24
Merge branch 'main' into main
christofilojohn Nov 20, 2025
d00a48d
Merge branch 'main' into main
christofilojohn Nov 20, 2025
c34e384
fixed readme greptile recommendations
christofilojohn Nov 20, 2025
757e7a1
Merge branch 'main' of https://github.com/christofilojohn/NVFlare
christofilojohn Nov 20, 2025
3eb7645
Merge branch 'main' into main
christofilojohn Nov 20, 2025
acde3a5
Merge branch 'main' into main
christofilojohn Nov 21, 2025
e86b2a5
Merge branch 'main' into main
christofilojohn Nov 22, 2025
687a253
fixed load_client_data mistake, Improved more accurate README file
christofilojohn Nov 22, 2025
5f5ebcc
changed evaluate_model method to public
christofilojohn Nov 22, 2025
5923d66
Reformatted files based on CONTRIBUTING.md
christofilojohn Nov 22, 2025
984991e
Update examples/advanced/feature_election/flare_deployment.py
christofilojohn Nov 22, 2025
c2a3552
Update nvflare/app_opt/feature_election/executor.py
christofilojohn Nov 22, 2025
74e136b
documentation changes
christofilojohn Nov 22, 2025
b7f9b95
Merge branch 'main' into main
christofilojohn Nov 24, 2025
a208bb3
Merge branch 'main' into main
christofilojohn Nov 24, 2025
24847fa
Merge branch 'main' into main
chesterxgchen Nov 24, 2025
05616fb
cleanup on text, comments, newlines
christofilojohn Nov 24, 2025
1cd10c9
Merge branch 'main' of https://github.com/christofilojohn/NVFlare
christofilojohn Nov 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions examples/advanced/feature_election/INSTALLATION_NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Installation Notes for NVIDIA FLARE Maintainers

## Adding Feature Election to setup.py

When integrating this module, please add the following to NVFlare's `setup.py`:

### In `extras_require`:
```python
extras_require={
# ... existing extras ...

"feature_election": [
"scikit-learn>=1.0.0",
"PyImpetus>=0.0.6", # Optional advanced methods
],

# Or split into basic/advanced
"feature_election_basic": [
"scikit-learn>=1.0.0",
],

"feature_election_advanced": [
"scikit-learn>=1.0.0",
"PyImpetus>=0.0.6",
],
}
```

## User Installation

Then users can install with:
```bash
# Basic (most common)
pip install nvflare[feature_election_basic]

# Advanced (with PyImpetus)
pip install nvflare[feature_election_advanced]

# Or install everything
pip install nvflare[feature_election]
```

## Rationale

- scikit-learn is widely available
- PyImpetus is optional for advanced permutation-based feature selection
- Module works without PyImpetus (gracefully degrades to standard methods)
248 changes: 248 additions & 0 deletions examples/advanced/feature_election/basic_usage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
"""
Basic Usage Example for Feature Election in NVIDIA FLARE

This example demonstrates the simplest way to use Feature Election
for federated feature selection on tabular datasets.
"""

import pandas as pd
import numpy as np
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, f1_score
from nvflare.app_opt.feature_election import quick_election


def create_sample_dataset():
"""Create a sample high-dimensional dataset"""
X, y = make_classification(
n_samples=1000,
n_features=100,
n_informative=20,
n_redundant=30,
n_repeated=10,
random_state=42
)

# Create meaningful feature names
feature_names = [f"feature_{i:03d}" for i in range(100)]
df = pd.DataFrame(X, columns=feature_names)
df['target'] = y

print(f"Created dataset: {df.shape[0]} samples, {df.shape[1]-1} features")
return df


def example_1_quick_start():
"""Example 1: Quickstart - simplest usage"""
print("\n" + "="*60)
print("Example 1: Quick Start")
print("="*60)

# Create dataset
df = create_sample_dataset()

# Run Feature Election with just one line!
selected_mask, stats = quick_election(
df=df,
target_col='target',
num_clients=4,
fs_method='lasso',
auto_tune=True
)

# Print results
print(f"\nOriginal features: {stats['num_features_original']}")
print(f"Selected features: {stats['num_features_selected']}")
print(f"Reduction: {stats['reduction_ratio']:.1%}")
print(f"Optimal freedom_degree: {stats['freedom_degree']:.2f}")

# Get selected feature names
feature_names = [col for col in df.columns if col != 'target']
selected_features = [feature_names[i] for i, selected in enumerate(selected_mask) if selected]
print(f"\nFirst 10 selected features: {selected_features[:10]}")


def example_2_with_evaluation():
"""Example 2: With model evaluation"""
print("\n" + "="*60)
print("Example 2: With Model Evaluation")
print("="*60)

# Create dataset
df = create_sample_dataset()

# Split data
X = df.drop('target', axis=1)
y = df['target']
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)

# Prepare DataFrame for feature election (using training data only)
df_train = X_train.copy()
df_train['target'] = y_train

# Run Feature Election
selected_mask, stats = quick_election(
df=df_train,
target_col='target',
num_clients=4,
fs_method='lasso',
auto_tune=True
)

# Apply mask to get selected features
X_train_selected = X_train.iloc[:, selected_mask]
X_test_selected = X_test.iloc[:, selected_mask]

# Train models
print("\nTraining models...")

# Model with all features
clf_all = RandomForestClassifier(n_estimators=100, random_state=42)
clf_all.fit(X_train, y_train)
y_pred_all = clf_all.predict(X_test)

# Model with selected features
clf_selected = RandomForestClassifier(n_estimators=100, random_state=42)
clf_selected.fit(X_train_selected, y_train)
y_pred_selected = clf_selected.predict(X_test_selected)

# Compare results
print("\nResults:")
print("-" * 60)
print(f"{'Metric':<20} {'All Features':<20} {'Selected Features':<20}")
print("-" * 60)
print(f"{'Accuracy':<20} {accuracy_score(y_test, y_pred_all):<20.4f} {accuracy_score(y_test, y_pred_selected):<20.4f}")
print(f"{'F1 Score':<20} {f1_score(y_test, y_pred_all):<20.4f} {f1_score(y_test, y_pred_selected):<20.4f}")
print(f"{'# Features':<20} {X_train.shape[1]:<20} {X_train_selected.shape[1]:<20}")
print("-" * 60)


def example_3_custom_configuration():
"""Example 3: Custom configuration"""
print("\n" + "="*60)
print("Example 3: Custom Configuration")
print("="*60)

from nvflare.app_opt.feature_election import FeatureElection

# Create dataset
df = create_sample_dataset()

# Initialize with custom parameters
fe = FeatureElection(
freedom_degree=0.6,
fs_method='elastic_net',
aggregation_mode='weighted'
)

# Prepare data splits
client_data = fe.prepare_data_splits(
df=df,
target_col='target',
num_clients=5,
split_strategy='stratified'
)

print(f"Prepared data for {len(client_data)} clients")
for i, (X, y) in enumerate(client_data):
print(f" Client {i+1}: {len(X)} samples, class distribution: {y.value_counts().to_dict()}")

# Run election
stats = fe.simulate_election(client_data)

# Print results
print(f"\nElection Results:")
print(f" Features selected: {stats['num_features_selected']}/{stats['num_features_original']}")
print(f" Reduction: {stats['reduction_ratio']:.1%}")
print(f" Intersection features: {stats['intersection_features']}")
print(f" Union features: {stats['union_features']}")

# Print client statistics
print(f"\nPer-Client Statistics:")
for client_name, client_stats in stats['client_stats'].items():
print(f" {client_name}:")
print(f" Features selected: {client_stats['num_selected']}")
print(f" Score improvement: {client_stats['improvement']:+.4f}")

# Save results
fe.save_results("feature_election_results.json")
print("\n✓ Results saved to feature_election_results.json")


def example_4_different_methods():
"""Example 4: Compare different feature selection methods"""
print("\n" + "="*60)
print("Example 4: Comparing Different FS Methods")
print("="*60)

# Create dataset
df = create_sample_dataset()

methods = ['lasso', 'elastic_net', 'random_forest', 'mutual_info', 'f_classif']
results = {}

for method in methods:
print(f"\nTesting {method}...")
selected_mask, stats = quick_election(
df=df,
target_col='target',
num_clients=4,
fs_method=method,
auto_tune=False,
freedom_degree=0.5
)

results[method] = {
'selected': stats['num_features_selected'],
'reduction': stats['reduction_ratio'],
'intersection': stats['intersection_features'],
'union': stats['union_features']
}

# Display comparison
print("\n" + "="*60)
print("Method Comparison")
print("="*60)
print(f"{'Method':<15} {'Selected':<12} {'Reduction':<12} {'Intersection':<12} {'Union':<10}")
print("-" * 60)
for method, res in results.items():
print(f"{method:<15} {res['selected']:<12} {res['reduction']:<11.1%} {res['intersection']:<12} {res['union']:<10}")


def main():
"""Run all examples"""
print("\n" + "="*70)
print(" Feature Election for NVIDIA FLARE - Basic Examples")
print("="*70)

try:
example_1_quick_start()
except Exception as e:
print(f"Example 1 failed: {e}")

try:
example_2_with_evaluation()
except Exception as e:
print(f"Example 2 failed: {e}")

try:
example_3_custom_configuration()
except Exception as e:
print(f"Example 3 failed: {e}")

try:
example_4_different_methods()
except Exception as e:
print(f"Example 4 failed: {e}")

print("\n" + "="*70)
print(" All examples completed!")
print("="*70)


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