churnkit 0.75.0a1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/00_start_here.ipynb +647 -0
- churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/01_data_discovery.ipynb +1165 -0
- churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/01a_a_temporal_text_deep_dive.ipynb +961 -0
- churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/01a_temporal_deep_dive.ipynb +1690 -0
- churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/01b_temporal_quality.ipynb +679 -0
- churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/01c_temporal_patterns.ipynb +3305 -0
- churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/01d_event_aggregation.ipynb +1463 -0
- churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/02_column_deep_dive.ipynb +1430 -0
- churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/02a_text_columns_deep_dive.ipynb +854 -0
- churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/03_quality_assessment.ipynb +1639 -0
- churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/04_relationship_analysis.ipynb +1890 -0
- churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/05_multi_dataset.ipynb +1457 -0
- churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/06_feature_opportunities.ipynb +1624 -0
- churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/07_modeling_readiness.ipynb +780 -0
- churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/08_baseline_experiments.ipynb +979 -0
- churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/09_business_alignment.ipynb +572 -0
- churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/10_spec_generation.ipynb +1179 -0
- churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/11_scoring_validation.ipynb +1418 -0
- churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/12_view_documentation.ipynb +151 -0
- churnkit-0.75.0a1.dist-info/METADATA +229 -0
- churnkit-0.75.0a1.dist-info/RECORD +302 -0
- churnkit-0.75.0a1.dist-info/WHEEL +4 -0
- churnkit-0.75.0a1.dist-info/entry_points.txt +2 -0
- churnkit-0.75.0a1.dist-info/licenses/LICENSE +202 -0
- customer_retention/__init__.py +37 -0
- customer_retention/analysis/__init__.py +0 -0
- customer_retention/analysis/auto_explorer/__init__.py +62 -0
- customer_retention/analysis/auto_explorer/exploration_manager.py +470 -0
- customer_retention/analysis/auto_explorer/explorer.py +258 -0
- customer_retention/analysis/auto_explorer/findings.py +291 -0
- customer_retention/analysis/auto_explorer/layered_recommendations.py +485 -0
- customer_retention/analysis/auto_explorer/recommendation_builder.py +148 -0
- customer_retention/analysis/auto_explorer/recommendations.py +418 -0
- customer_retention/analysis/business/__init__.py +26 -0
- customer_retention/analysis/business/ab_test_designer.py +144 -0
- customer_retention/analysis/business/fairness_analyzer.py +166 -0
- customer_retention/analysis/business/intervention_matcher.py +121 -0
- customer_retention/analysis/business/report_generator.py +222 -0
- customer_retention/analysis/business/risk_profile.py +199 -0
- customer_retention/analysis/business/roi_analyzer.py +139 -0
- customer_retention/analysis/diagnostics/__init__.py +20 -0
- customer_retention/analysis/diagnostics/calibration_analyzer.py +133 -0
- customer_retention/analysis/diagnostics/cv_analyzer.py +144 -0
- customer_retention/analysis/diagnostics/error_analyzer.py +107 -0
- customer_retention/analysis/diagnostics/leakage_detector.py +394 -0
- customer_retention/analysis/diagnostics/noise_tester.py +140 -0
- customer_retention/analysis/diagnostics/overfitting_analyzer.py +190 -0
- customer_retention/analysis/diagnostics/segment_analyzer.py +122 -0
- customer_retention/analysis/discovery/__init__.py +8 -0
- customer_retention/analysis/discovery/config_generator.py +49 -0
- customer_retention/analysis/discovery/discovery_flow.py +19 -0
- customer_retention/analysis/discovery/type_inferencer.py +147 -0
- customer_retention/analysis/interpretability/__init__.py +13 -0
- customer_retention/analysis/interpretability/cohort_analyzer.py +185 -0
- customer_retention/analysis/interpretability/counterfactual.py +175 -0
- customer_retention/analysis/interpretability/individual_explainer.py +141 -0
- customer_retention/analysis/interpretability/pdp_generator.py +103 -0
- customer_retention/analysis/interpretability/shap_explainer.py +106 -0
- customer_retention/analysis/jupyter_save_hook.py +28 -0
- customer_retention/analysis/notebook_html_exporter.py +136 -0
- customer_retention/analysis/notebook_progress.py +60 -0
- customer_retention/analysis/plotly_preprocessor.py +154 -0
- customer_retention/analysis/recommendations/__init__.py +54 -0
- customer_retention/analysis/recommendations/base.py +158 -0
- customer_retention/analysis/recommendations/cleaning/__init__.py +11 -0
- customer_retention/analysis/recommendations/cleaning/consistency.py +107 -0
- customer_retention/analysis/recommendations/cleaning/deduplicate.py +94 -0
- customer_retention/analysis/recommendations/cleaning/impute.py +67 -0
- customer_retention/analysis/recommendations/cleaning/outlier.py +71 -0
- customer_retention/analysis/recommendations/datetime/__init__.py +3 -0
- customer_retention/analysis/recommendations/datetime/extract.py +149 -0
- customer_retention/analysis/recommendations/encoding/__init__.py +3 -0
- customer_retention/analysis/recommendations/encoding/categorical.py +114 -0
- customer_retention/analysis/recommendations/pipeline.py +74 -0
- customer_retention/analysis/recommendations/registry.py +76 -0
- customer_retention/analysis/recommendations/selection/__init__.py +3 -0
- customer_retention/analysis/recommendations/selection/drop_column.py +56 -0
- customer_retention/analysis/recommendations/transform/__init__.py +4 -0
- customer_retention/analysis/recommendations/transform/power.py +94 -0
- customer_retention/analysis/recommendations/transform/scale.py +112 -0
- customer_retention/analysis/visualization/__init__.py +15 -0
- customer_retention/analysis/visualization/chart_builder.py +2619 -0
- customer_retention/analysis/visualization/console.py +122 -0
- customer_retention/analysis/visualization/display.py +171 -0
- customer_retention/analysis/visualization/number_formatter.py +36 -0
- customer_retention/artifacts/__init__.py +3 -0
- customer_retention/artifacts/fit_artifact_registry.py +146 -0
- customer_retention/cli.py +93 -0
- customer_retention/core/__init__.py +0 -0
- customer_retention/core/compat/__init__.py +193 -0
- customer_retention/core/compat/detection.py +99 -0
- customer_retention/core/compat/ops.py +48 -0
- customer_retention/core/compat/pandas_backend.py +57 -0
- customer_retention/core/compat/spark_backend.py +75 -0
- customer_retention/core/components/__init__.py +11 -0
- customer_retention/core/components/base.py +79 -0
- customer_retention/core/components/components/__init__.py +13 -0
- customer_retention/core/components/components/deployer.py +26 -0
- customer_retention/core/components/components/explainer.py +26 -0
- customer_retention/core/components/components/feature_eng.py +33 -0
- customer_retention/core/components/components/ingester.py +34 -0
- customer_retention/core/components/components/profiler.py +34 -0
- customer_retention/core/components/components/trainer.py +38 -0
- customer_retention/core/components/components/transformer.py +36 -0
- customer_retention/core/components/components/validator.py +37 -0
- customer_retention/core/components/enums.py +33 -0
- customer_retention/core/components/orchestrator.py +94 -0
- customer_retention/core/components/registry.py +59 -0
- customer_retention/core/config/__init__.py +39 -0
- customer_retention/core/config/column_config.py +95 -0
- customer_retention/core/config/experiments.py +71 -0
- customer_retention/core/config/pipeline_config.py +117 -0
- customer_retention/core/config/source_config.py +83 -0
- customer_retention/core/utils/__init__.py +28 -0
- customer_retention/core/utils/leakage.py +85 -0
- customer_retention/core/utils/severity.py +53 -0
- customer_retention/core/utils/statistics.py +90 -0
- customer_retention/generators/__init__.py +0 -0
- customer_retention/generators/notebook_generator/__init__.py +167 -0
- customer_retention/generators/notebook_generator/base.py +55 -0
- customer_retention/generators/notebook_generator/cell_builder.py +49 -0
- customer_retention/generators/notebook_generator/config.py +47 -0
- customer_retention/generators/notebook_generator/databricks_generator.py +48 -0
- customer_retention/generators/notebook_generator/local_generator.py +48 -0
- customer_retention/generators/notebook_generator/project_init.py +174 -0
- customer_retention/generators/notebook_generator/runner.py +150 -0
- customer_retention/generators/notebook_generator/script_generator.py +110 -0
- customer_retention/generators/notebook_generator/stages/__init__.py +19 -0
- customer_retention/generators/notebook_generator/stages/base_stage.py +86 -0
- customer_retention/generators/notebook_generator/stages/s01_ingestion.py +100 -0
- customer_retention/generators/notebook_generator/stages/s02_profiling.py +95 -0
- customer_retention/generators/notebook_generator/stages/s03_cleaning.py +180 -0
- customer_retention/generators/notebook_generator/stages/s04_transformation.py +165 -0
- customer_retention/generators/notebook_generator/stages/s05_feature_engineering.py +115 -0
- customer_retention/generators/notebook_generator/stages/s06_feature_selection.py +97 -0
- customer_retention/generators/notebook_generator/stages/s07_model_training.py +176 -0
- customer_retention/generators/notebook_generator/stages/s08_deployment.py +81 -0
- customer_retention/generators/notebook_generator/stages/s09_monitoring.py +112 -0
- customer_retention/generators/notebook_generator/stages/s10_batch_inference.py +642 -0
- customer_retention/generators/notebook_generator/stages/s11_feature_store.py +348 -0
- customer_retention/generators/orchestration/__init__.py +23 -0
- customer_retention/generators/orchestration/code_generator.py +196 -0
- customer_retention/generators/orchestration/context.py +147 -0
- customer_retention/generators/orchestration/data_materializer.py +188 -0
- customer_retention/generators/orchestration/databricks_exporter.py +411 -0
- customer_retention/generators/orchestration/doc_generator.py +311 -0
- customer_retention/generators/pipeline_generator/__init__.py +26 -0
- customer_retention/generators/pipeline_generator/findings_parser.py +727 -0
- customer_retention/generators/pipeline_generator/generator.py +142 -0
- customer_retention/generators/pipeline_generator/models.py +166 -0
- customer_retention/generators/pipeline_generator/renderer.py +2125 -0
- customer_retention/generators/spec_generator/__init__.py +37 -0
- customer_retention/generators/spec_generator/databricks_generator.py +433 -0
- customer_retention/generators/spec_generator/generic_generator.py +373 -0
- customer_retention/generators/spec_generator/mlflow_pipeline_generator.py +685 -0
- customer_retention/generators/spec_generator/pipeline_spec.py +298 -0
- customer_retention/integrations/__init__.py +0 -0
- customer_retention/integrations/adapters/__init__.py +13 -0
- customer_retention/integrations/adapters/base.py +10 -0
- customer_retention/integrations/adapters/factory.py +25 -0
- customer_retention/integrations/adapters/feature_store/__init__.py +6 -0
- customer_retention/integrations/adapters/feature_store/base.py +57 -0
- customer_retention/integrations/adapters/feature_store/databricks.py +94 -0
- customer_retention/integrations/adapters/feature_store/feast_adapter.py +97 -0
- customer_retention/integrations/adapters/feature_store/local.py +75 -0
- customer_retention/integrations/adapters/mlflow/__init__.py +6 -0
- customer_retention/integrations/adapters/mlflow/base.py +32 -0
- customer_retention/integrations/adapters/mlflow/databricks.py +54 -0
- customer_retention/integrations/adapters/mlflow/experiment_tracker.py +161 -0
- customer_retention/integrations/adapters/mlflow/local.py +50 -0
- customer_retention/integrations/adapters/storage/__init__.py +5 -0
- customer_retention/integrations/adapters/storage/base.py +33 -0
- customer_retention/integrations/adapters/storage/databricks.py +76 -0
- customer_retention/integrations/adapters/storage/local.py +59 -0
- customer_retention/integrations/feature_store/__init__.py +47 -0
- customer_retention/integrations/feature_store/definitions.py +215 -0
- customer_retention/integrations/feature_store/manager.py +744 -0
- customer_retention/integrations/feature_store/registry.py +412 -0
- customer_retention/integrations/iteration/__init__.py +28 -0
- customer_retention/integrations/iteration/context.py +212 -0
- customer_retention/integrations/iteration/feedback_collector.py +184 -0
- customer_retention/integrations/iteration/orchestrator.py +168 -0
- customer_retention/integrations/iteration/recommendation_tracker.py +341 -0
- customer_retention/integrations/iteration/signals.py +212 -0
- customer_retention/integrations/llm_context/__init__.py +4 -0
- customer_retention/integrations/llm_context/context_builder.py +201 -0
- customer_retention/integrations/llm_context/prompts.py +100 -0
- customer_retention/integrations/streaming/__init__.py +103 -0
- customer_retention/integrations/streaming/batch_integration.py +149 -0
- customer_retention/integrations/streaming/early_warning_model.py +227 -0
- customer_retention/integrations/streaming/event_schema.py +214 -0
- customer_retention/integrations/streaming/online_store_writer.py +249 -0
- customer_retention/integrations/streaming/realtime_scorer.py +261 -0
- customer_retention/integrations/streaming/trigger_engine.py +293 -0
- customer_retention/integrations/streaming/window_aggregator.py +393 -0
- customer_retention/stages/__init__.py +0 -0
- customer_retention/stages/cleaning/__init__.py +9 -0
- customer_retention/stages/cleaning/base.py +28 -0
- customer_retention/stages/cleaning/missing_handler.py +160 -0
- customer_retention/stages/cleaning/outlier_handler.py +204 -0
- customer_retention/stages/deployment/__init__.py +28 -0
- customer_retention/stages/deployment/batch_scorer.py +106 -0
- customer_retention/stages/deployment/champion_challenger.py +299 -0
- customer_retention/stages/deployment/model_registry.py +182 -0
- customer_retention/stages/deployment/retraining_trigger.py +245 -0
- customer_retention/stages/features/__init__.py +73 -0
- customer_retention/stages/features/behavioral_features.py +266 -0
- customer_retention/stages/features/customer_segmentation.py +505 -0
- customer_retention/stages/features/feature_definitions.py +265 -0
- customer_retention/stages/features/feature_engineer.py +551 -0
- customer_retention/stages/features/feature_manifest.py +340 -0
- customer_retention/stages/features/feature_selector.py +239 -0
- customer_retention/stages/features/interaction_features.py +160 -0
- customer_retention/stages/features/temporal_features.py +243 -0
- customer_retention/stages/ingestion/__init__.py +9 -0
- customer_retention/stages/ingestion/load_result.py +32 -0
- customer_retention/stages/ingestion/loaders.py +195 -0
- customer_retention/stages/ingestion/source_registry.py +130 -0
- customer_retention/stages/modeling/__init__.py +31 -0
- customer_retention/stages/modeling/baseline_trainer.py +139 -0
- customer_retention/stages/modeling/cross_validator.py +125 -0
- customer_retention/stages/modeling/data_splitter.py +205 -0
- customer_retention/stages/modeling/feature_scaler.py +99 -0
- customer_retention/stages/modeling/hyperparameter_tuner.py +107 -0
- customer_retention/stages/modeling/imbalance_handler.py +282 -0
- customer_retention/stages/modeling/mlflow_logger.py +95 -0
- customer_retention/stages/modeling/model_comparator.py +149 -0
- customer_retention/stages/modeling/model_evaluator.py +138 -0
- customer_retention/stages/modeling/threshold_optimizer.py +131 -0
- customer_retention/stages/monitoring/__init__.py +37 -0
- customer_retention/stages/monitoring/alert_manager.py +328 -0
- customer_retention/stages/monitoring/drift_detector.py +201 -0
- customer_retention/stages/monitoring/performance_monitor.py +242 -0
- customer_retention/stages/preprocessing/__init__.py +5 -0
- customer_retention/stages/preprocessing/transformer_manager.py +284 -0
- customer_retention/stages/profiling/__init__.py +256 -0
- customer_retention/stages/profiling/categorical_distribution.py +269 -0
- customer_retention/stages/profiling/categorical_target_analyzer.py +274 -0
- customer_retention/stages/profiling/column_profiler.py +527 -0
- customer_retention/stages/profiling/distribution_analysis.py +483 -0
- customer_retention/stages/profiling/drift_detector.py +310 -0
- customer_retention/stages/profiling/feature_capacity.py +507 -0
- customer_retention/stages/profiling/pattern_analysis_config.py +513 -0
- customer_retention/stages/profiling/profile_result.py +212 -0
- customer_retention/stages/profiling/quality_checks.py +1632 -0
- customer_retention/stages/profiling/relationship_detector.py +256 -0
- customer_retention/stages/profiling/relationship_recommender.py +454 -0
- customer_retention/stages/profiling/report_generator.py +520 -0
- customer_retention/stages/profiling/scd_analyzer.py +151 -0
- customer_retention/stages/profiling/segment_analyzer.py +632 -0
- customer_retention/stages/profiling/segment_aware_outlier.py +265 -0
- customer_retention/stages/profiling/target_level_analyzer.py +217 -0
- customer_retention/stages/profiling/temporal_analyzer.py +388 -0
- customer_retention/stages/profiling/temporal_coverage.py +488 -0
- customer_retention/stages/profiling/temporal_feature_analyzer.py +692 -0
- customer_retention/stages/profiling/temporal_feature_engineer.py +703 -0
- customer_retention/stages/profiling/temporal_pattern_analyzer.py +636 -0
- customer_retention/stages/profiling/temporal_quality_checks.py +278 -0
- customer_retention/stages/profiling/temporal_target_analyzer.py +241 -0
- customer_retention/stages/profiling/text_embedder.py +87 -0
- customer_retention/stages/profiling/text_processor.py +115 -0
- customer_retention/stages/profiling/text_reducer.py +60 -0
- customer_retention/stages/profiling/time_series_profiler.py +303 -0
- customer_retention/stages/profiling/time_window_aggregator.py +376 -0
- customer_retention/stages/profiling/type_detector.py +382 -0
- customer_retention/stages/profiling/window_recommendation.py +288 -0
- customer_retention/stages/temporal/__init__.py +166 -0
- customer_retention/stages/temporal/access_guard.py +180 -0
- customer_retention/stages/temporal/cutoff_analyzer.py +235 -0
- customer_retention/stages/temporal/data_preparer.py +178 -0
- customer_retention/stages/temporal/point_in_time_join.py +134 -0
- customer_retention/stages/temporal/point_in_time_registry.py +148 -0
- customer_retention/stages/temporal/scenario_detector.py +163 -0
- customer_retention/stages/temporal/snapshot_manager.py +259 -0
- customer_retention/stages/temporal/synthetic_coordinator.py +66 -0
- customer_retention/stages/temporal/timestamp_discovery.py +531 -0
- customer_retention/stages/temporal/timestamp_manager.py +255 -0
- customer_retention/stages/transformation/__init__.py +13 -0
- customer_retention/stages/transformation/binary_handler.py +85 -0
- customer_retention/stages/transformation/categorical_encoder.py +245 -0
- customer_retention/stages/transformation/datetime_transformer.py +97 -0
- customer_retention/stages/transformation/numeric_transformer.py +181 -0
- customer_retention/stages/transformation/pipeline.py +257 -0
- customer_retention/stages/validation/__init__.py +60 -0
- customer_retention/stages/validation/adversarial_scoring_validator.py +205 -0
- customer_retention/stages/validation/business_sense_gate.py +173 -0
- customer_retention/stages/validation/data_quality_gate.py +235 -0
- customer_retention/stages/validation/data_validators.py +511 -0
- customer_retention/stages/validation/feature_quality_gate.py +183 -0
- customer_retention/stages/validation/gates.py +117 -0
- customer_retention/stages/validation/leakage_gate.py +352 -0
- customer_retention/stages/validation/model_validity_gate.py +213 -0
- customer_retention/stages/validation/pipeline_validation_runner.py +264 -0
- customer_retention/stages/validation/quality_scorer.py +544 -0
- customer_retention/stages/validation/rule_generator.py +57 -0
- customer_retention/stages/validation/scoring_pipeline_validator.py +446 -0
- customer_retention/stages/validation/timeseries_detector.py +769 -0
- customer_retention/transforms/__init__.py +47 -0
- customer_retention/transforms/artifact_store.py +50 -0
- customer_retention/transforms/executor.py +157 -0
- customer_retention/transforms/fitted.py +92 -0
- customer_retention/transforms/ops.py +148 -0
churnkit-0.75.0a1.data/data/share/churnkit/exploration_notebooks/08_baseline_experiments.ipynb
ADDED
|
@@ -0,0 +1,979 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cells": [
|
|
3
|
+
{
|
|
4
|
+
"cell_type": "markdown",
|
|
5
|
+
"id": "74ed4553",
|
|
6
|
+
"metadata": {
|
|
7
|
+
"papermill": {
|
|
8
|
+
"duration": 0.003306,
|
|
9
|
+
"end_time": "2026-02-02T13:03:41.579279",
|
|
10
|
+
"exception": false,
|
|
11
|
+
"start_time": "2026-02-02T13:03:41.575973",
|
|
12
|
+
"status": "completed"
|
|
13
|
+
},
|
|
14
|
+
"tags": []
|
|
15
|
+
},
|
|
16
|
+
"source": [
|
|
17
|
+
"# Chapter 8: Baseline Experiments\n",
|
|
18
|
+
"\n",
|
|
19
|
+
"**Purpose:** Train baseline models to understand data predictability and establish performance benchmarks.\n",
|
|
20
|
+
"\n",
|
|
21
|
+
"**What you'll learn:**\n",
|
|
22
|
+
"- How to prepare data for ML with proper train/test splitting\n",
|
|
23
|
+
"- How to handle class imbalance with class weights\n",
|
|
24
|
+
"- How to evaluate models with appropriate metrics (not just accuracy!)\n",
|
|
25
|
+
"- How to interpret feature importance\n",
|
|
26
|
+
"\n",
|
|
27
|
+
"**Outputs:**\n",
|
|
28
|
+
"- Baseline model performance (AUC, Precision, Recall, F1)\n",
|
|
29
|
+
"- Feature importance rankings\n",
|
|
30
|
+
"- ROC and Precision-Recall curves\n",
|
|
31
|
+
"- Performance benchmarks for comparison\n",
|
|
32
|
+
"\n",
|
|
33
|
+
"---\n",
|
|
34
|
+
"\n",
|
|
35
|
+
"## Evaluation Metrics for Imbalanced Data\n",
|
|
36
|
+
"\n",
|
|
37
|
+
"| Metric | What It Measures | When to Use |\n",
|
|
38
|
+
"|--------|-----------------|-------------|\n",
|
|
39
|
+
"| **AUC-ROC** | Ranking quality across thresholds | General model comparison |\n",
|
|
40
|
+
"| **Precision** | \"Of predicted churned, how many are correct?\" | When false positives are costly |\n",
|
|
41
|
+
"| **Recall** | \"Of actual churned, how many did we catch?\" | When missing churners is costly |\n",
|
|
42
|
+
"| **F1-Score** | Balance of precision and recall | When both matter equally |\n",
|
|
43
|
+
"| **PR-AUC** | Precision-Recall under curve | Better for imbalanced data |"
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"cell_type": "markdown",
|
|
48
|
+
"id": "28b04efa",
|
|
49
|
+
"metadata": {
|
|
50
|
+
"papermill": {
|
|
51
|
+
"duration": 0.002464,
|
|
52
|
+
"end_time": "2026-02-02T13:03:41.584582",
|
|
53
|
+
"exception": false,
|
|
54
|
+
"start_time": "2026-02-02T13:03:41.582118",
|
|
55
|
+
"status": "completed"
|
|
56
|
+
},
|
|
57
|
+
"tags": []
|
|
58
|
+
},
|
|
59
|
+
"source": [
|
|
60
|
+
"## 8.1 Setup"
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"cell_type": "code",
|
|
65
|
+
"execution_count": null,
|
|
66
|
+
"id": "65e6e683",
|
|
67
|
+
"metadata": {
|
|
68
|
+
"execution": {
|
|
69
|
+
"iopub.execute_input": "2026-02-02T13:03:41.590074Z",
|
|
70
|
+
"iopub.status.busy": "2026-02-02T13:03:41.589907Z",
|
|
71
|
+
"iopub.status.idle": "2026-02-02T13:03:43.387358Z",
|
|
72
|
+
"shell.execute_reply": "2026-02-02T13:03:43.386225Z"
|
|
73
|
+
},
|
|
74
|
+
"papermill": {
|
|
75
|
+
"duration": 1.801175,
|
|
76
|
+
"end_time": "2026-02-02T13:03:43.388139",
|
|
77
|
+
"exception": false,
|
|
78
|
+
"start_time": "2026-02-02T13:03:41.586964",
|
|
79
|
+
"status": "completed"
|
|
80
|
+
},
|
|
81
|
+
"tags": []
|
|
82
|
+
},
|
|
83
|
+
"outputs": [],
|
|
84
|
+
"source": [
|
|
85
|
+
"from customer_retention.analysis.notebook_progress import track_and_export_previous\n",
|
|
86
|
+
"track_and_export_previous(\"08_baseline_experiments.ipynb\")\n",
|
|
87
|
+
"\n",
|
|
88
|
+
"from customer_retention.analysis.auto_explorer import ExplorationFindings\n",
|
|
89
|
+
"from customer_retention.analysis.visualization import ChartBuilder, display_figure, display_table\n",
|
|
90
|
+
"from customer_retention.core.config.column_config import ColumnType\n",
|
|
91
|
+
"import pandas as pd\n",
|
|
92
|
+
"import numpy as np\n",
|
|
93
|
+
"\n",
|
|
94
|
+
"from sklearn.model_selection import train_test_split, cross_val_score\n",
|
|
95
|
+
"from sklearn.preprocessing import StandardScaler, LabelEncoder\n",
|
|
96
|
+
"from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier\n",
|
|
97
|
+
"from sklearn.linear_model import LogisticRegression\n",
|
|
98
|
+
"from sklearn.metrics import (roc_auc_score, classification_report, confusion_matrix,\n",
|
|
99
|
+
" roc_curve, precision_recall_curve, average_precision_score,\n",
|
|
100
|
+
" f1_score, precision_score, recall_score)\n",
|
|
101
|
+
"\n",
|
|
102
|
+
"import plotly.graph_objects as go\n",
|
|
103
|
+
"from plotly.subplots import make_subplots\n",
|
|
104
|
+
"from customer_retention.core.config.experiments import FINDINGS_DIR, EXPERIMENTS_DIR, OUTPUT_DIR, setup_experiments_structure"
|
|
105
|
+
]
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"cell_type": "code",
|
|
109
|
+
"execution_count": null,
|
|
110
|
+
"id": "f8e78a64",
|
|
111
|
+
"metadata": {
|
|
112
|
+
"execution": {
|
|
113
|
+
"iopub.execute_input": "2026-02-02T13:03:43.394005Z",
|
|
114
|
+
"iopub.status.busy": "2026-02-02T13:03:43.393872Z",
|
|
115
|
+
"iopub.status.idle": "2026-02-02T13:03:43.711417Z",
|
|
116
|
+
"shell.execute_reply": "2026-02-02T13:03:43.710865Z"
|
|
117
|
+
},
|
|
118
|
+
"papermill": {
|
|
119
|
+
"duration": 0.322219,
|
|
120
|
+
"end_time": "2026-02-02T13:03:43.712580",
|
|
121
|
+
"exception": false,
|
|
122
|
+
"start_time": "2026-02-02T13:03:43.390361",
|
|
123
|
+
"status": "completed"
|
|
124
|
+
},
|
|
125
|
+
"tags": []
|
|
126
|
+
},
|
|
127
|
+
"outputs": [],
|
|
128
|
+
"source": [
|
|
129
|
+
"# === CONFIGURATION ===\n",
|
|
130
|
+
"from pathlib import Path\n",
|
|
131
|
+
"\n",
|
|
132
|
+
"# FINDINGS_DIR imported from customer_retention.core.config.experiments\n",
|
|
133
|
+
"\n",
|
|
134
|
+
"findings_files = [f for f in FINDINGS_DIR.glob(\"*_findings.yaml\") if \"multi_dataset\" not in f.name]\n",
|
|
135
|
+
"if not findings_files:\n",
|
|
136
|
+
" raise FileNotFoundError(f\"No findings files found in {FINDINGS_DIR}. Run notebook 01 first.\")\n",
|
|
137
|
+
"\n",
|
|
138
|
+
"# Prefer aggregated findings (from 01d) over event-level findings\n",
|
|
139
|
+
"# Pattern: *_aggregated* in filename indicates aggregated data\n",
|
|
140
|
+
"aggregated_files = [f for f in findings_files if \"_aggregated\" in f.name]\n",
|
|
141
|
+
"non_aggregated_files = [f for f in findings_files if \"_aggregated\" not in f.name]\n",
|
|
142
|
+
"\n",
|
|
143
|
+
"if aggregated_files:\n",
|
|
144
|
+
" # Use most recent aggregated file\n",
|
|
145
|
+
" aggregated_files.sort(key=lambda f: f.stat().st_mtime, reverse=True)\n",
|
|
146
|
+
" FINDINGS_PATH = str(aggregated_files[0])\n",
|
|
147
|
+
" print(f\"Found {len(aggregated_files)} aggregated findings file(s)\")\n",
|
|
148
|
+
" print(f\"Using: {FINDINGS_PATH}\")\n",
|
|
149
|
+
" if non_aggregated_files:\n",
|
|
150
|
+
" print(f\" (Skipping {len(non_aggregated_files)} event-level findings)\")\n",
|
|
151
|
+
"else:\n",
|
|
152
|
+
" # Fall back to most recent non-aggregated file\n",
|
|
153
|
+
" non_aggregated_files.sort(key=lambda f: f.stat().st_mtime, reverse=True)\n",
|
|
154
|
+
" FINDINGS_PATH = str(non_aggregated_files[0])\n",
|
|
155
|
+
" print(f\"Found {len(findings_files)} findings file(s)\")\n",
|
|
156
|
+
" print(f\"Using: {FINDINGS_PATH}\")\n",
|
|
157
|
+
"\n",
|
|
158
|
+
"findings = ExplorationFindings.load(FINDINGS_PATH)\n",
|
|
159
|
+
"\n",
|
|
160
|
+
"# Load data - handle aggregated vs standard paths\n",
|
|
161
|
+
"from customer_retention.stages.temporal import load_data_with_snapshot_preference, TEMPORAL_METADATA_COLS\n",
|
|
162
|
+
"\n",
|
|
163
|
+
"# For aggregated data, load directly from the parquet source\n",
|
|
164
|
+
"if \"_aggregated\" in FINDINGS_PATH and findings.source_path.endswith('.parquet'):\n",
|
|
165
|
+
" source_path = Path(findings.source_path)\n",
|
|
166
|
+
" # Handle relative path from notebook directory\n",
|
|
167
|
+
" if not source_path.is_absolute():\n",
|
|
168
|
+
" # The source_path in findings is relative to project root\n",
|
|
169
|
+
" if str(source_path).startswith(\"experiments\"):\n",
|
|
170
|
+
" source_path = Path(\"..\") / source_path\n",
|
|
171
|
+
" else:\n",
|
|
172
|
+
" source_path = FINDINGS_DIR / source_path.name\n",
|
|
173
|
+
" df = pd.read_parquet(source_path)\n",
|
|
174
|
+
" data_source = f\"aggregated:{source_path.name}\"\n",
|
|
175
|
+
"else:\n",
|
|
176
|
+
" # Standard loading for event-level or entity-level data\n",
|
|
177
|
+
" df, data_source = load_data_with_snapshot_preference(findings, output_dir=str(FINDINGS_DIR))\n",
|
|
178
|
+
"\n",
|
|
179
|
+
"charts = ChartBuilder()\n",
|
|
180
|
+
"\n",
|
|
181
|
+
"print(f\"\\nLoaded {len(df):,} rows from: {data_source}\")"
|
|
182
|
+
]
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
"cell_type": "markdown",
|
|
186
|
+
"id": "1dac203f",
|
|
187
|
+
"metadata": {
|
|
188
|
+
"papermill": {
|
|
189
|
+
"duration": 0.001718,
|
|
190
|
+
"end_time": "2026-02-02T13:03:43.716671",
|
|
191
|
+
"exception": false,
|
|
192
|
+
"start_time": "2026-02-02T13:03:43.714953",
|
|
193
|
+
"status": "completed"
|
|
194
|
+
},
|
|
195
|
+
"tags": []
|
|
196
|
+
},
|
|
197
|
+
"source": [
|
|
198
|
+
"## 8.2 Prepare Data for Modeling\n",
|
|
199
|
+
"\n",
|
|
200
|
+
"**📖 Feature Source:**\n",
|
|
201
|
+
"\n",
|
|
202
|
+
"Features used in this notebook come from the **ExplorationFindings** generated in earlier notebooks:\n",
|
|
203
|
+
"- Column types are **auto-detected** in notebook 01 (Data Discovery)\n",
|
|
204
|
+
"- Target column is identified from the findings\n",
|
|
205
|
+
"- Identifier columns are **excluded** to prevent data leakage\n",
|
|
206
|
+
"- Text columns are **excluded** (require specialized NLP processing)\n",
|
|
207
|
+
"\n",
|
|
208
|
+
"**📖 Best Practices:**\n",
|
|
209
|
+
"1. **Stratified Split**: Maintains class ratios in train/test sets\n",
|
|
210
|
+
"2. **Scale After Split**: Fit scaler on train only (prevents data leakage)\n",
|
|
211
|
+
"3. **Handle Missing**: Impute or drop before scaling\n",
|
|
212
|
+
"\n",
|
|
213
|
+
"**📖 Transformations Applied:**\n",
|
|
214
|
+
"- Categorical variables → Label Encoded\n",
|
|
215
|
+
"- Missing values → Median (numeric) or Mode (categorical)\n",
|
|
216
|
+
"- Features → StandardScaler (fit on train only)"
|
|
217
|
+
]
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"cell_type": "code",
|
|
221
|
+
"execution_count": null,
|
|
222
|
+
"id": "b0882dff",
|
|
223
|
+
"metadata": {
|
|
224
|
+
"execution": {
|
|
225
|
+
"iopub.execute_input": "2026-02-02T13:03:43.720878Z",
|
|
226
|
+
"iopub.status.busy": "2026-02-02T13:03:43.720755Z",
|
|
227
|
+
"iopub.status.idle": "2026-02-02T13:03:43.725238Z",
|
|
228
|
+
"shell.execute_reply": "2026-02-02T13:03:43.724436Z"
|
|
229
|
+
},
|
|
230
|
+
"papermill": {
|
|
231
|
+
"duration": 0.007805,
|
|
232
|
+
"end_time": "2026-02-02T13:03:43.726115",
|
|
233
|
+
"exception": false,
|
|
234
|
+
"start_time": "2026-02-02T13:03:43.718310",
|
|
235
|
+
"status": "completed"
|
|
236
|
+
},
|
|
237
|
+
"tags": []
|
|
238
|
+
},
|
|
239
|
+
"outputs": [],
|
|
240
|
+
"source": [
|
|
241
|
+
"if not findings.target_column:\n",
|
|
242
|
+
" raise ValueError(\"No target column set. Please define one in exploration notebooks.\")\n",
|
|
243
|
+
"\n",
|
|
244
|
+
"target = findings.target_column\n",
|
|
245
|
+
"y = df[target]\n",
|
|
246
|
+
"\n",
|
|
247
|
+
"# Features are selected based on column types from ExplorationFindings\n",
|
|
248
|
+
"# This ensures consistency with earlier notebooks and prevents data leakage\n",
|
|
249
|
+
"feature_cols = [\n",
|
|
250
|
+
" name for name, col in findings.columns.items()\n",
|
|
251
|
+
" if col.inferred_type not in [ColumnType.IDENTIFIER, ColumnType.TARGET, ColumnType.TEXT]\n",
|
|
252
|
+
" and name not in TEMPORAL_METADATA_COLS # Exclude point-in-time columns from features\n",
|
|
253
|
+
"]\n",
|
|
254
|
+
"\n",
|
|
255
|
+
"print(\"=\" * 70)\n",
|
|
256
|
+
"print(\"FEATURE SELECTION FROM FINDINGS\")\n",
|
|
257
|
+
"print(\"=\" * 70)\n",
|
|
258
|
+
"print(f\"\\n🎯 Target Column: {target}\")\n",
|
|
259
|
+
"print(f\"📊 Features Selected: {len(feature_cols)}\")\n",
|
|
260
|
+
"\n",
|
|
261
|
+
"# Show feature breakdown by type\n",
|
|
262
|
+
"type_counts = {}\n",
|
|
263
|
+
"for name in feature_cols:\n",
|
|
264
|
+
" col_type = findings.columns[name].inferred_type.value\n",
|
|
265
|
+
" type_counts[col_type] = type_counts.get(col_type, 0) + 1\n",
|
|
266
|
+
"\n",
|
|
267
|
+
"print(\"\\n📋 Features by Type:\")\n",
|
|
268
|
+
"for col_type, count in sorted(type_counts.items()):\n",
|
|
269
|
+
" print(f\" {col_type}: {count}\")\n",
|
|
270
|
+
"\n",
|
|
271
|
+
"# Show excluded columns\n",
|
|
272
|
+
"excluded = [name for name, col in findings.columns.items() \n",
|
|
273
|
+
" if col.inferred_type in [ColumnType.IDENTIFIER, ColumnType.TARGET, ColumnType.TEXT]]\n",
|
|
274
|
+
"if excluded:\n",
|
|
275
|
+
" print(f\"\\n⛔ Excluded Columns: {', '.join(excluded)}\")"
|
|
276
|
+
]
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
"cell_type": "code",
|
|
280
|
+
"execution_count": null,
|
|
281
|
+
"id": "47ad17e5",
|
|
282
|
+
"metadata": {
|
|
283
|
+
"execution": {
|
|
284
|
+
"iopub.execute_input": "2026-02-02T13:03:43.735220Z",
|
|
285
|
+
"iopub.status.busy": "2026-02-02T13:03:43.735017Z",
|
|
286
|
+
"iopub.status.idle": "2026-02-02T13:03:43.753043Z",
|
|
287
|
+
"shell.execute_reply": "2026-02-02T13:03:43.748353Z"
|
|
288
|
+
},
|
|
289
|
+
"papermill": {
|
|
290
|
+
"duration": 0.026325,
|
|
291
|
+
"end_time": "2026-02-02T13:03:43.754902",
|
|
292
|
+
"exception": false,
|
|
293
|
+
"start_time": "2026-02-02T13:03:43.728577",
|
|
294
|
+
"status": "completed"
|
|
295
|
+
},
|
|
296
|
+
"tags": []
|
|
297
|
+
},
|
|
298
|
+
"outputs": [],
|
|
299
|
+
"source": [
|
|
300
|
+
"# Check feature availability and remove problematic features\n",
|
|
301
|
+
"from customer_retention.stages.features.feature_selector import FeatureSelector\n",
|
|
302
|
+
"\n",
|
|
303
|
+
"print(\"=\" * 70)\n",
|
|
304
|
+
"print(\"FEATURE AVAILABILITY CHECK\")\n",
|
|
305
|
+
"print(\"=\" * 70)\n",
|
|
306
|
+
"\n",
|
|
307
|
+
"unavailable_features = []\n",
|
|
308
|
+
"if findings.has_availability_issues:\n",
|
|
309
|
+
" selector = FeatureSelector(target_column=findings.target_column)\n",
|
|
310
|
+
" availability_recs = selector.get_availability_recommendations(findings.feature_availability)\n",
|
|
311
|
+
" unavailable_features = [rec.column for rec in availability_recs]\n",
|
|
312
|
+
" \n",
|
|
313
|
+
" print(f\"\\n⚠️ {len(availability_recs)} feature(s) have availability issues:\\n\")\n",
|
|
314
|
+
" for rec in availability_recs:\n",
|
|
315
|
+
" print(f\" • {rec.column} ({rec.issue_type}, {rec.coverage_pct:.0f}% coverage)\")\n",
|
|
316
|
+
" \n",
|
|
317
|
+
" print(\"\\n📋 Alternative approaches (for investigation):\")\n",
|
|
318
|
+
" print(\" • segment_by_cohort: Train separate models per availability period\")\n",
|
|
319
|
+
" print(\" • add_indicator: Create availability flags and impute missing\")\n",
|
|
320
|
+
" print(\" • filter_window: Restrict data to feature's available period\")\n",
|
|
321
|
+
" \n",
|
|
322
|
+
" original_count = len(feature_cols)\n",
|
|
323
|
+
" feature_cols = [f for f in feature_cols if f not in unavailable_features]\n",
|
|
324
|
+
" \n",
|
|
325
|
+
" print(f\"\\n🗑️ Removed {original_count - len(feature_cols)} unavailable features\")\n",
|
|
326
|
+
" print(f\"📊 Features remaining: {len(feature_cols)}\")\n",
|
|
327
|
+
"else:\n",
|
|
328
|
+
" print(\"\\n✅ All features have full temporal coverage.\")"
|
|
329
|
+
]
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
"cell_type": "code",
|
|
333
|
+
"execution_count": null,
|
|
334
|
+
"id": "5cc3c542",
|
|
335
|
+
"metadata": {
|
|
336
|
+
"execution": {
|
|
337
|
+
"iopub.execute_input": "2026-02-02T13:03:43.767637Z",
|
|
338
|
+
"iopub.status.busy": "2026-02-02T13:03:43.767512Z",
|
|
339
|
+
"iopub.status.idle": "2026-02-02T13:03:43.790766Z",
|
|
340
|
+
"shell.execute_reply": "2026-02-02T13:03:43.790207Z"
|
|
341
|
+
},
|
|
342
|
+
"papermill": {
|
|
343
|
+
"duration": 0.026743,
|
|
344
|
+
"end_time": "2026-02-02T13:03:43.791461",
|
|
345
|
+
"exception": false,
|
|
346
|
+
"start_time": "2026-02-02T13:03:43.764718",
|
|
347
|
+
"status": "completed"
|
|
348
|
+
},
|
|
349
|
+
"tags": []
|
|
350
|
+
},
|
|
351
|
+
"outputs": [],
|
|
352
|
+
"source": [
|
|
353
|
+
"X = df[feature_cols].copy()\n",
|
|
354
|
+
"\n",
|
|
355
|
+
"# Encode categorical variables\n",
|
|
356
|
+
"for col in X.select_dtypes(include=['object']).columns:\n",
|
|
357
|
+
" le = LabelEncoder()\n",
|
|
358
|
+
" X[col] = le.fit_transform(X[col].astype(str))\n",
|
|
359
|
+
"\n",
|
|
360
|
+
"# Handle missing values (median for numeric, mode for others)\n",
|
|
361
|
+
"for col in X.columns:\n",
|
|
362
|
+
" if X[col].isnull().any():\n",
|
|
363
|
+
" if X[col].dtype in ['int64', 'float64']:\n",
|
|
364
|
+
" X[col] = X[col].fillna(X[col].median())\n",
|
|
365
|
+
" else:\n",
|
|
366
|
+
" X[col] = X[col].fillna(X[col].mode()[0])\n",
|
|
367
|
+
"\n",
|
|
368
|
+
"# Stratified train/test split (maintains class distribution)\n",
|
|
369
|
+
"X_train, X_test, y_train, y_test = train_test_split(\n",
|
|
370
|
+
" X, y, test_size=0.2, random_state=42, stratify=y\n",
|
|
371
|
+
")\n",
|
|
372
|
+
"\n",
|
|
373
|
+
"# Scale features (fit on train only!)\n",
|
|
374
|
+
"scaler = StandardScaler()\n",
|
|
375
|
+
"X_train_scaled = pd.DataFrame(scaler.fit_transform(X_train), columns=X_train.columns)\n",
|
|
376
|
+
"X_test_scaled = pd.DataFrame(scaler.transform(X_test), columns=X_test.columns)\n",
|
|
377
|
+
"\n",
|
|
378
|
+
"print(f\"Train size: {len(X_train):,} ({len(X_train)/len(X)*100:.0f}%)\")\n",
|
|
379
|
+
"print(f\"Test size: {len(X_test):,} ({len(X_test)/len(X)*100:.0f}%)\")\n",
|
|
380
|
+
"print(f\"\\nTrain class distribution:\")\n",
|
|
381
|
+
"print(f\" Retained (1): {(y_train == 1).sum():,} ({(y_train == 1).sum()/len(y_train)*100:.1f}%)\")\n",
|
|
382
|
+
"print(f\" Churned (0): {(y_train == 0).sum():,} ({(y_train == 0).sum()/len(y_train)*100:.1f}%)\")"
|
|
383
|
+
]
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
"cell_type": "markdown",
|
|
387
|
+
"id": "4abd8fd2",
|
|
388
|
+
"metadata": {
|
|
389
|
+
"papermill": {
|
|
390
|
+
"duration": 0.001721,
|
|
391
|
+
"end_time": "2026-02-02T13:03:43.795234",
|
|
392
|
+
"exception": false,
|
|
393
|
+
"start_time": "2026-02-02T13:03:43.793513",
|
|
394
|
+
"status": "completed"
|
|
395
|
+
},
|
|
396
|
+
"tags": []
|
|
397
|
+
},
|
|
398
|
+
"source": [
|
|
399
|
+
"## 8.3 Baseline Models (with Class Weights)\n",
|
|
400
|
+
"\n",
|
|
401
|
+
"**📖 Using Class Weights:**\n",
|
|
402
|
+
"- `class_weight='balanced'` automatically adjusts weights inversely proportional to class frequencies\n",
|
|
403
|
+
"- This helps models pay more attention to the minority class (churned customers)\n",
|
|
404
|
+
"- Without weights, models may just predict \"retained\" for everyone"
|
|
405
|
+
]
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
"cell_type": "code",
|
|
409
|
+
"execution_count": null,
|
|
410
|
+
"id": "0c4db3a7",
|
|
411
|
+
"metadata": {
|
|
412
|
+
"execution": {
|
|
413
|
+
"iopub.execute_input": "2026-02-02T13:03:43.800931Z",
|
|
414
|
+
"iopub.status.busy": "2026-02-02T13:03:43.800775Z",
|
|
415
|
+
"iopub.status.idle": "2026-02-02T13:03:51.241619Z",
|
|
416
|
+
"shell.execute_reply": "2026-02-02T13:03:51.241076Z"
|
|
417
|
+
},
|
|
418
|
+
"papermill": {
|
|
419
|
+
"duration": 7.444712,
|
|
420
|
+
"end_time": "2026-02-02T13:03:51.242480",
|
|
421
|
+
"exception": false,
|
|
422
|
+
"start_time": "2026-02-02T13:03:43.797768",
|
|
423
|
+
"status": "completed"
|
|
424
|
+
},
|
|
425
|
+
"tags": []
|
|
426
|
+
},
|
|
427
|
+
"outputs": [],
|
|
428
|
+
"source": [
|
|
429
|
+
"# Models with class_weight='balanced' to handle imbalance\n",
|
|
430
|
+
"models = {\n",
|
|
431
|
+
" \"Logistic Regression\": LogisticRegression(max_iter=1000, random_state=42, class_weight='balanced'),\n",
|
|
432
|
+
" \"Random Forest\": RandomForestClassifier(n_estimators=100, random_state=42, n_jobs=-1, class_weight='balanced'),\n",
|
|
433
|
+
" \"Gradient Boosting\": GradientBoostingClassifier(n_estimators=100, random_state=42)\n",
|
|
434
|
+
"}\n",
|
|
435
|
+
"\n",
|
|
436
|
+
"results = []\n",
|
|
437
|
+
"model_predictions = {}\n",
|
|
438
|
+
"\n",
|
|
439
|
+
"for name, model in models.items():\n",
|
|
440
|
+
" print(f\"Training {name}...\")\n",
|
|
441
|
+
" \n",
|
|
442
|
+
" # Use scaled data for Logistic Regression, unscaled for tree-based\n",
|
|
443
|
+
" if \"Logistic\" in name:\n",
|
|
444
|
+
" model.fit(X_train_scaled, y_train)\n",
|
|
445
|
+
" y_pred = model.predict(X_test_scaled)\n",
|
|
446
|
+
" y_pred_proba = model.predict_proba(X_test_scaled)[:, 1]\n",
|
|
447
|
+
" else:\n",
|
|
448
|
+
" model.fit(X_train, y_train)\n",
|
|
449
|
+
" y_pred = model.predict(X_test)\n",
|
|
450
|
+
" y_pred_proba = model.predict_proba(X_test)[:, 1]\n",
|
|
451
|
+
" \n",
|
|
452
|
+
" # Calculate metrics\n",
|
|
453
|
+
" auc = roc_auc_score(y_test, y_pred_proba)\n",
|
|
454
|
+
" pr_auc = average_precision_score(y_test, y_pred_proba)\n",
|
|
455
|
+
" f1 = f1_score(y_test, y_pred)\n",
|
|
456
|
+
" precision = precision_score(y_test, y_pred)\n",
|
|
457
|
+
" recall = recall_score(y_test, y_pred)\n",
|
|
458
|
+
" \n",
|
|
459
|
+
" # Cross-validation\n",
|
|
460
|
+
" if \"Logistic\" in name:\n",
|
|
461
|
+
" cv_scores = cross_val_score(model, X_train_scaled, y_train, cv=5, scoring='roc_auc')\n",
|
|
462
|
+
" else:\n",
|
|
463
|
+
" cv_scores = cross_val_score(model, X_train, y_train, cv=5, scoring='roc_auc')\n",
|
|
464
|
+
" \n",
|
|
465
|
+
" results.append({\n",
|
|
466
|
+
" \"Model\": name,\n",
|
|
467
|
+
" \"Test AUC\": auc,\n",
|
|
468
|
+
" \"PR-AUC\": pr_auc,\n",
|
|
469
|
+
" \"F1-Score\": f1,\n",
|
|
470
|
+
" \"Precision\": precision,\n",
|
|
471
|
+
" \"Recall\": recall,\n",
|
|
472
|
+
" \"CV AUC Mean\": cv_scores.mean(),\n",
|
|
473
|
+
" \"CV AUC Std\": cv_scores.std()\n",
|
|
474
|
+
" })\n",
|
|
475
|
+
" \n",
|
|
476
|
+
" model_predictions[name] = {\n",
|
|
477
|
+
" 'y_pred': y_pred,\n",
|
|
478
|
+
" 'y_pred_proba': y_pred_proba,\n",
|
|
479
|
+
" 'model': model\n",
|
|
480
|
+
" }\n",
|
|
481
|
+
"\n",
|
|
482
|
+
"results_df = pd.DataFrame(results)\n",
|
|
483
|
+
"results_df = results_df.round(4)\n",
|
|
484
|
+
"\n",
|
|
485
|
+
"print(\"\\n\" + \"=\" * 80)\n",
|
|
486
|
+
"print(\"MODEL COMPARISON\")\n",
|
|
487
|
+
"print(\"=\" * 80)\n",
|
|
488
|
+
"display_table(results_df)"
|
|
489
|
+
]
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
"cell_type": "markdown",
|
|
493
|
+
"id": "134b2fd5",
|
|
494
|
+
"metadata": {
|
|
495
|
+
"papermill": {
|
|
496
|
+
"duration": 0.002387,
|
|
497
|
+
"end_time": "2026-02-02T13:03:51.247546",
|
|
498
|
+
"exception": false,
|
|
499
|
+
"start_time": "2026-02-02T13:03:51.245159",
|
|
500
|
+
"status": "completed"
|
|
501
|
+
},
|
|
502
|
+
"tags": []
|
|
503
|
+
},
|
|
504
|
+
"source": [
|
|
505
|
+
"## 8.4 Feature Importance (Random Forest)"
|
|
506
|
+
]
|
|
507
|
+
},
|
|
508
|
+
{
|
|
509
|
+
"cell_type": "code",
|
|
510
|
+
"execution_count": null,
|
|
511
|
+
"id": "c0adf3a4",
|
|
512
|
+
"metadata": {
|
|
513
|
+
"execution": {
|
|
514
|
+
"iopub.execute_input": "2026-02-02T13:03:51.252747Z",
|
|
515
|
+
"iopub.status.busy": "2026-02-02T13:03:51.252629Z",
|
|
516
|
+
"iopub.status.idle": "2026-02-02T13:03:51.281884Z",
|
|
517
|
+
"shell.execute_reply": "2026-02-02T13:03:51.281477Z"
|
|
518
|
+
},
|
|
519
|
+
"papermill": {
|
|
520
|
+
"duration": 0.032888,
|
|
521
|
+
"end_time": "2026-02-02T13:03:51.282770",
|
|
522
|
+
"exception": false,
|
|
523
|
+
"start_time": "2026-02-02T13:03:51.249882",
|
|
524
|
+
"status": "completed"
|
|
525
|
+
},
|
|
526
|
+
"tags": []
|
|
527
|
+
},
|
|
528
|
+
"outputs": [],
|
|
529
|
+
"source": [
|
|
530
|
+
"rf_model = models[\"Random Forest\"]\n",
|
|
531
|
+
"importance_df = pd.DataFrame({\n",
|
|
532
|
+
" \"Feature\": feature_cols,\n",
|
|
533
|
+
" \"Importance\": rf_model.feature_importances_\n",
|
|
534
|
+
"}).sort_values(\"Importance\", ascending=False)\n",
|
|
535
|
+
"\n",
|
|
536
|
+
"top_n = 15\n",
|
|
537
|
+
"top_features = importance_df.head(top_n)\n",
|
|
538
|
+
"\n",
|
|
539
|
+
"fig = charts.bar_chart(\n",
|
|
540
|
+
" top_features[\"Feature\"].tolist(),\n",
|
|
541
|
+
" top_features[\"Importance\"].tolist(),\n",
|
|
542
|
+
" title=f\"Top {top_n} Feature Importances\"\n",
|
|
543
|
+
")\n",
|
|
544
|
+
"display_figure(fig)"
|
|
545
|
+
]
|
|
546
|
+
},
|
|
547
|
+
{
|
|
548
|
+
"cell_type": "markdown",
|
|
549
|
+
"id": "7ced338c",
|
|
550
|
+
"metadata": {
|
|
551
|
+
"papermill": {
|
|
552
|
+
"duration": 0.003509,
|
|
553
|
+
"end_time": "2026-02-02T13:03:51.290387",
|
|
554
|
+
"exception": false,
|
|
555
|
+
"start_time": "2026-02-02T13:03:51.286878",
|
|
556
|
+
"status": "completed"
|
|
557
|
+
},
|
|
558
|
+
"tags": []
|
|
559
|
+
},
|
|
560
|
+
"source": [
|
|
561
|
+
"## 8.5 Classification Report (Best Model)"
|
|
562
|
+
]
|
|
563
|
+
},
|
|
564
|
+
{
|
|
565
|
+
"cell_type": "code",
|
|
566
|
+
"execution_count": null,
|
|
567
|
+
"id": "28691087",
|
|
568
|
+
"metadata": {
|
|
569
|
+
"execution": {
|
|
570
|
+
"iopub.execute_input": "2026-02-02T13:03:51.298626Z",
|
|
571
|
+
"iopub.status.busy": "2026-02-02T13:03:51.298513Z",
|
|
572
|
+
"iopub.status.idle": "2026-02-02T13:03:51.306713Z",
|
|
573
|
+
"shell.execute_reply": "2026-02-02T13:03:51.306286Z"
|
|
574
|
+
},
|
|
575
|
+
"papermill": {
|
|
576
|
+
"duration": 0.013151,
|
|
577
|
+
"end_time": "2026-02-02T13:03:51.307450",
|
|
578
|
+
"exception": false,
|
|
579
|
+
"start_time": "2026-02-02T13:03:51.294299",
|
|
580
|
+
"status": "completed"
|
|
581
|
+
},
|
|
582
|
+
"tags": []
|
|
583
|
+
},
|
|
584
|
+
"outputs": [],
|
|
585
|
+
"source": [
|
|
586
|
+
"best_model = models[\"Gradient Boosting\"]\n",
|
|
587
|
+
"y_pred = best_model.predict(X_test)\n",
|
|
588
|
+
"\n",
|
|
589
|
+
"print(\"Classification Report (Gradient Boosting):\")\n",
|
|
590
|
+
"print(classification_report(y_test, y_pred))"
|
|
591
|
+
]
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
"cell_type": "markdown",
|
|
595
|
+
"id": "de19bff3",
|
|
596
|
+
"metadata": {
|
|
597
|
+
"papermill": {
|
|
598
|
+
"duration": 0.003638,
|
|
599
|
+
"end_time": "2026-02-02T13:03:51.315003",
|
|
600
|
+
"exception": false,
|
|
601
|
+
"start_time": "2026-02-02T13:03:51.311365",
|
|
602
|
+
"status": "completed"
|
|
603
|
+
},
|
|
604
|
+
"tags": []
|
|
605
|
+
},
|
|
606
|
+
"source": [
|
|
607
|
+
"## 8.6 Model Comparison Grid\n",
|
|
608
|
+
"\n",
|
|
609
|
+
"This visualization shows all models side-by-side with:\n",
|
|
610
|
+
"- **Row 1**: Confusion matrices (counts and percentages)\n",
|
|
611
|
+
"- **Row 2**: ROC curves with AUC scores\n",
|
|
612
|
+
"- **Row 3**: Precision-Recall curves with PR-AUC scores\n",
|
|
613
|
+
"\n",
|
|
614
|
+
"**📖 How to Read:**\n",
|
|
615
|
+
"- **Confusion Matrix**: Diagonal = correct predictions. Off-diagonal = errors.\n",
|
|
616
|
+
"- **ROC Curve**: Higher curve = better. AUC > 0.8 is good, > 0.9 is excellent.\n",
|
|
617
|
+
"- **PR Curve**: Higher curve = better at finding positives without false alarms."
|
|
618
|
+
]
|
|
619
|
+
},
|
|
620
|
+
{
|
|
621
|
+
"cell_type": "code",
|
|
622
|
+
"execution_count": null,
|
|
623
|
+
"id": "ab41b3bb",
|
|
624
|
+
"metadata": {
|
|
625
|
+
"execution": {
|
|
626
|
+
"iopub.execute_input": "2026-02-02T13:03:51.322228Z",
|
|
627
|
+
"iopub.status.busy": "2026-02-02T13:03:51.322098Z",
|
|
628
|
+
"iopub.status.idle": "2026-02-02T13:03:51.405939Z",
|
|
629
|
+
"shell.execute_reply": "2026-02-02T13:03:51.405558Z"
|
|
630
|
+
},
|
|
631
|
+
"papermill": {
|
|
632
|
+
"duration": 0.088886,
|
|
633
|
+
"end_time": "2026-02-02T13:03:51.407134",
|
|
634
|
+
"exception": false,
|
|
635
|
+
"start_time": "2026-02-02T13:03:51.318248",
|
|
636
|
+
"status": "completed"
|
|
637
|
+
},
|
|
638
|
+
"tags": []
|
|
639
|
+
},
|
|
640
|
+
"outputs": [],
|
|
641
|
+
"source": [
|
|
642
|
+
"# Create comprehensive model comparison grid\n",
|
|
643
|
+
"# Uses the framework's ChartBuilder.model_comparison_grid method\n",
|
|
644
|
+
"\n",
|
|
645
|
+
"# Prepare model results in the expected format\n",
|
|
646
|
+
"grid_results = {\n",
|
|
647
|
+
" name: {\n",
|
|
648
|
+
" \"y_pred\": data[\"y_pred\"],\n",
|
|
649
|
+
" \"y_pred_proba\": data[\"y_pred_proba\"]\n",
|
|
650
|
+
" }\n",
|
|
651
|
+
" for name, data in model_predictions.items()\n",
|
|
652
|
+
"}\n",
|
|
653
|
+
"\n",
|
|
654
|
+
"# Create the visualization grid\n",
|
|
655
|
+
"fig = charts.model_comparison_grid(\n",
|
|
656
|
+
" grid_results,\n",
|
|
657
|
+
" y_test,\n",
|
|
658
|
+
" class_labels=[\"Churned (0)\", \"Retained (1)\"],\n",
|
|
659
|
+
" title=\"Model Comparison: Confusion Matrix | ROC Curve | Precision-Recall\"\n",
|
|
660
|
+
")\n",
|
|
661
|
+
"\n",
|
|
662
|
+
"display_figure(fig)\n",
|
|
663
|
+
"\n",
|
|
664
|
+
"# Summary metrics table\n",
|
|
665
|
+
"print(\"\\n\" + \"=\" * 80)\n",
|
|
666
|
+
"print(\"METRICS SUMMARY\")\n",
|
|
667
|
+
"print(\"=\" * 80)\n",
|
|
668
|
+
"display_table(results_df[[\"Model\", \"Test AUC\", \"PR-AUC\", \"F1-Score\", \"Precision\", \"Recall\"]])"
|
|
669
|
+
]
|
|
670
|
+
},
|
|
671
|
+
{
|
|
672
|
+
"cell_type": "markdown",
|
|
673
|
+
"id": "8644b753",
|
|
674
|
+
"metadata": {
|
|
675
|
+
"papermill": {
|
|
676
|
+
"duration": 0.006319,
|
|
677
|
+
"end_time": "2026-02-02T13:03:51.420670",
|
|
678
|
+
"exception": false,
|
|
679
|
+
"start_time": "2026-02-02T13:03:51.414351",
|
|
680
|
+
"status": "completed"
|
|
681
|
+
},
|
|
682
|
+
"tags": []
|
|
683
|
+
},
|
|
684
|
+
"source": [
|
|
685
|
+
"### 8.6.1 Individual Model Analysis\n",
|
|
686
|
+
"\n",
|
|
687
|
+
"The grid above shows all models together. Below is detailed analysis per model."
|
|
688
|
+
]
|
|
689
|
+
},
|
|
690
|
+
{
|
|
691
|
+
"cell_type": "code",
|
|
692
|
+
"execution_count": null,
|
|
693
|
+
"id": "912a2e57",
|
|
694
|
+
"metadata": {
|
|
695
|
+
"execution": {
|
|
696
|
+
"iopub.execute_input": "2026-02-02T13:03:51.436032Z",
|
|
697
|
+
"iopub.status.busy": "2026-02-02T13:03:51.435905Z",
|
|
698
|
+
"iopub.status.idle": "2026-02-02T13:03:51.446884Z",
|
|
699
|
+
"shell.execute_reply": "2026-02-02T13:03:51.446378Z"
|
|
700
|
+
},
|
|
701
|
+
"papermill": {
|
|
702
|
+
"duration": 0.019499,
|
|
703
|
+
"end_time": "2026-02-02T13:03:51.447385",
|
|
704
|
+
"exception": false,
|
|
705
|
+
"start_time": "2026-02-02T13:03:51.427886",
|
|
706
|
+
"status": "completed"
|
|
707
|
+
},
|
|
708
|
+
"tags": []
|
|
709
|
+
},
|
|
710
|
+
"outputs": [],
|
|
711
|
+
"source": [
|
|
712
|
+
"# Individual model classification reports\n",
|
|
713
|
+
"print(\"=\" * 70)\n",
|
|
714
|
+
"print(\"CLASSIFICATION REPORTS BY MODEL\")\n",
|
|
715
|
+
"print(\"=\" * 70)\n",
|
|
716
|
+
"\n",
|
|
717
|
+
"for name, data in model_predictions.items():\n",
|
|
718
|
+
" print(f\"\\n{'='*40}\")\n",
|
|
719
|
+
" print(f\"📊 {name}\")\n",
|
|
720
|
+
" print('='*40)\n",
|
|
721
|
+
" print(classification_report(y_test, data['y_pred'], target_names=[\"Churned\", \"Retained\"]))"
|
|
722
|
+
]
|
|
723
|
+
},
|
|
724
|
+
{
|
|
725
|
+
"cell_type": "markdown",
|
|
726
|
+
"id": "84d4fa6c",
|
|
727
|
+
"metadata": {
|
|
728
|
+
"papermill": {
|
|
729
|
+
"duration": 0.007249,
|
|
730
|
+
"end_time": "2026-02-02T13:03:51.461923",
|
|
731
|
+
"exception": false,
|
|
732
|
+
"start_time": "2026-02-02T13:03:51.454674",
|
|
733
|
+
"status": "completed"
|
|
734
|
+
},
|
|
735
|
+
"tags": []
|
|
736
|
+
},
|
|
737
|
+
"source": [
|
|
738
|
+
"### 8.6.1 Precision-Recall Curves\n",
|
|
739
|
+
"\n",
|
|
740
|
+
"**📖 Why PR Curves for Imbalanced Data:**\n",
|
|
741
|
+
"- ROC curves can look optimistic for imbalanced data\n",
|
|
742
|
+
"- PR curves focus on the minority class (churners)\n",
|
|
743
|
+
"- Better at showing how well we detect actual churners\n",
|
|
744
|
+
"\n",
|
|
745
|
+
"**📖 How to Read:**\n",
|
|
746
|
+
"- **Baseline** (dashed line) = proportion of positives in the data\n",
|
|
747
|
+
"- Higher curve = better at finding churners without too many false alarms"
|
|
748
|
+
]
|
|
749
|
+
},
|
|
750
|
+
{
|
|
751
|
+
"cell_type": "markdown",
|
|
752
|
+
"id": "61320a60",
|
|
753
|
+
"metadata": {
|
|
754
|
+
"papermill": {
|
|
755
|
+
"duration": 0.00658,
|
|
756
|
+
"end_time": "2026-02-02T13:03:51.475265",
|
|
757
|
+
"exception": false,
|
|
758
|
+
"start_time": "2026-02-02T13:03:51.468685",
|
|
759
|
+
"status": "completed"
|
|
760
|
+
},
|
|
761
|
+
"tags": []
|
|
762
|
+
},
|
|
763
|
+
"source": [
|
|
764
|
+
"## 8.7 Key Takeaways\n",
|
|
765
|
+
"\n",
|
|
766
|
+
"**📖 Interpreting Results:**"
|
|
767
|
+
]
|
|
768
|
+
},
|
|
769
|
+
{
|
|
770
|
+
"cell_type": "code",
|
|
771
|
+
"execution_count": null,
|
|
772
|
+
"id": "f89502a1",
|
|
773
|
+
"metadata": {
|
|
774
|
+
"execution": {
|
|
775
|
+
"iopub.execute_input": "2026-02-02T13:03:51.490585Z",
|
|
776
|
+
"iopub.status.busy": "2026-02-02T13:03:51.490464Z",
|
|
777
|
+
"iopub.status.idle": "2026-02-02T13:03:51.494835Z",
|
|
778
|
+
"shell.execute_reply": "2026-02-02T13:03:51.494379Z"
|
|
779
|
+
},
|
|
780
|
+
"papermill": {
|
|
781
|
+
"duration": 0.012494,
|
|
782
|
+
"end_time": "2026-02-02T13:03:51.495294",
|
|
783
|
+
"exception": false,
|
|
784
|
+
"start_time": "2026-02-02T13:03:51.482800",
|
|
785
|
+
"status": "completed"
|
|
786
|
+
},
|
|
787
|
+
"tags": []
|
|
788
|
+
},
|
|
789
|
+
"outputs": [],
|
|
790
|
+
"source": [
|
|
791
|
+
"best_model = results_df.loc[results_df['Test AUC'].idxmax()]\n",
|
|
792
|
+
"\n",
|
|
793
|
+
"print(\"=\" * 70)\n",
|
|
794
|
+
"print(\"KEY TAKEAWAYS\")\n",
|
|
795
|
+
"print(\"=\" * 70)\n",
|
|
796
|
+
"\n",
|
|
797
|
+
"print(f\"\\n🏆 BEST MODEL: {best_model['Model']}\")\n",
|
|
798
|
+
"print(f\" Test AUC: {best_model['Test AUC']:.4f}\")\n",
|
|
799
|
+
"print(f\" PR-AUC: {best_model['PR-AUC']:.4f}\")\n",
|
|
800
|
+
"print(f\" F1-Score: {best_model['F1-Score']:.4f}\")\n",
|
|
801
|
+
"\n",
|
|
802
|
+
"print(f\"\\n📊 TOP 3 IMPORTANT FEATURES:\")\n",
|
|
803
|
+
"for i, feat in enumerate(importance_df.head(3)['Feature'].tolist(), 1):\n",
|
|
804
|
+
" imp = importance_df[importance_df['Feature'] == feat]['Importance'].values[0]\n",
|
|
805
|
+
" print(f\" {i}. {feat} ({imp:.3f})\")\n",
|
|
806
|
+
"\n",
|
|
807
|
+
"print(f\"\\n📈 MODEL PERFORMANCE ASSESSMENT:\")\n",
|
|
808
|
+
"if best_model['Test AUC'] > 0.90:\n",
|
|
809
|
+
" print(\" Excellent predictive signal - likely production-ready with tuning\")\n",
|
|
810
|
+
"elif best_model['Test AUC'] > 0.80:\n",
|
|
811
|
+
" print(\" Strong predictive signal - good baseline for improvement\")\n",
|
|
812
|
+
"elif best_model['Test AUC'] > 0.70:\n",
|
|
813
|
+
" print(\" Moderate signal - consider more feature engineering\")\n",
|
|
814
|
+
"else:\n",
|
|
815
|
+
" print(\" Weak signal - may need more data or different features\")\n",
|
|
816
|
+
"\n",
|
|
817
|
+
"print(f\"\\n💡 NEXT STEPS:\")\n",
|
|
818
|
+
"print(\" 1. Feature engineering with derived features (notebook 05)\")\n",
|
|
819
|
+
"print(\" 2. Hyperparameter tuning (GridSearchCV)\")\n",
|
|
820
|
+
"print(\" 3. Threshold optimization for business metrics\")\n",
|
|
821
|
+
"print(\" 4. A/B testing in production\")"
|
|
822
|
+
]
|
|
823
|
+
},
|
|
824
|
+
{
|
|
825
|
+
"cell_type": "markdown",
|
|
826
|
+
"id": "4351d95d",
|
|
827
|
+
"metadata": {
|
|
828
|
+
"papermill": {
|
|
829
|
+
"duration": 0.006771,
|
|
830
|
+
"end_time": "2026-02-02T13:03:51.509716",
|
|
831
|
+
"exception": false,
|
|
832
|
+
"start_time": "2026-02-02T13:03:51.502945",
|
|
833
|
+
"status": "completed"
|
|
834
|
+
},
|
|
835
|
+
"tags": []
|
|
836
|
+
},
|
|
837
|
+
"source": [
|
|
838
|
+
"---\n",
|
|
839
|
+
"\n",
|
|
840
|
+
"## Summary: What We Learned\n",
|
|
841
|
+
"\n",
|
|
842
|
+
"In this notebook, we trained baseline models and established performance benchmarks:\n",
|
|
843
|
+
"\n",
|
|
844
|
+
"1. **Data Preparation** - Proper train/test split with stratification and scaling\n",
|
|
845
|
+
"2. **Class Imbalance Handling** - Used balanced class weights\n",
|
|
846
|
+
"3. **Model Comparison** - Compared Logistic Regression, Random Forest, and Gradient Boosting\n",
|
|
847
|
+
"4. **Multiple Metrics** - Evaluated with AUC, PR-AUC, F1, Precision, Recall\n",
|
|
848
|
+
"5. **Feature Importance** - Identified the most predictive features\n",
|
|
849
|
+
"\n",
|
|
850
|
+
"## Key Results for This Dataset\n",
|
|
851
|
+
"\n",
|
|
852
|
+
"| Metric | Value | Interpretation |\n",
|
|
853
|
+
"|--------|-------|----------------|\n",
|
|
854
|
+
"| Best AUC | ~0.98 | Excellent discrimination |\n",
|
|
855
|
+
"| Top Feature | esent | Email engagement is critical |\n",
|
|
856
|
+
"| Imbalance | ~4:1 | Moderate, handled with class weights |\n",
|
|
857
|
+
"\n",
|
|
858
|
+
"---\n",
|
|
859
|
+
"\n",
|
|
860
|
+
"## Next Steps\n",
|
|
861
|
+
"\n",
|
|
862
|
+
"Continue to **09_business_alignment.ipynb** to:\n",
|
|
863
|
+
"- Align model performance with business objectives\n",
|
|
864
|
+
"- Define intervention strategies by risk level\n",
|
|
865
|
+
"- Calculate expected ROI from the model\n",
|
|
866
|
+
"- Set deployment requirements"
|
|
867
|
+
]
|
|
868
|
+
},
|
|
869
|
+
{
|
|
870
|
+
"cell_type": "code",
|
|
871
|
+
"execution_count": null,
|
|
872
|
+
"id": "87d90782",
|
|
873
|
+
"metadata": {
|
|
874
|
+
"execution": {
|
|
875
|
+
"iopub.execute_input": "2026-02-02T13:03:51.524206Z",
|
|
876
|
+
"iopub.status.busy": "2026-02-02T13:03:51.524094Z",
|
|
877
|
+
"iopub.status.idle": "2026-02-02T13:03:51.526600Z",
|
|
878
|
+
"shell.execute_reply": "2026-02-02T13:03:51.526165Z"
|
|
879
|
+
},
|
|
880
|
+
"papermill": {
|
|
881
|
+
"duration": 0.010354,
|
|
882
|
+
"end_time": "2026-02-02T13:03:51.527118",
|
|
883
|
+
"exception": false,
|
|
884
|
+
"start_time": "2026-02-02T13:03:51.516764",
|
|
885
|
+
"status": "completed"
|
|
886
|
+
},
|
|
887
|
+
"tags": []
|
|
888
|
+
},
|
|
889
|
+
"outputs": [],
|
|
890
|
+
"source": [
|
|
891
|
+
"best_auc = max(float(r[\"Test AUC\"]) for r in results)\n",
|
|
892
|
+
"\n",
|
|
893
|
+
"print(\"Key Takeaways:\")\n",
|
|
894
|
+
"print(\"=\"*50)\n",
|
|
895
|
+
"print(f\"Best baseline AUC: {best_auc:.4f}\")\n",
|
|
896
|
+
"print(f\"Top 3 important features: {', '.join(importance_df.head(3)['Feature'].tolist())}\")\n",
|
|
897
|
+
"\n",
|
|
898
|
+
"if best_auc > 0.85:\n",
|
|
899
|
+
" print(\"\\nStrong predictive signal detected. Data is well-suited for modeling.\")\n",
|
|
900
|
+
"elif best_auc > 0.70:\n",
|
|
901
|
+
" print(\"\\nModerate predictive signal. Consider feature engineering for improvement.\")\n",
|
|
902
|
+
"else:\n",
|
|
903
|
+
" print(\"\\nWeak predictive signal. May need more features or data.\")"
|
|
904
|
+
]
|
|
905
|
+
},
|
|
906
|
+
{
|
|
907
|
+
"cell_type": "markdown",
|
|
908
|
+
"id": "1a13b5b8",
|
|
909
|
+
"metadata": {
|
|
910
|
+
"papermill": {
|
|
911
|
+
"duration": 0.007176,
|
|
912
|
+
"end_time": "2026-02-02T13:03:51.541652",
|
|
913
|
+
"exception": false,
|
|
914
|
+
"start_time": "2026-02-02T13:03:51.534476",
|
|
915
|
+
"status": "completed"
|
|
916
|
+
},
|
|
917
|
+
"tags": []
|
|
918
|
+
},
|
|
919
|
+
"source": [
|
|
920
|
+
"---\n",
|
|
921
|
+
"\n",
|
|
922
|
+
"## Next Steps\n",
|
|
923
|
+
"\n",
|
|
924
|
+
"Continue to **09_business_alignment.ipynb** to align with business objectives."
|
|
925
|
+
]
|
|
926
|
+
},
|
|
927
|
+
{
|
|
928
|
+
"cell_type": "markdown",
|
|
929
|
+
"id": "18e9c769",
|
|
930
|
+
"metadata": {
|
|
931
|
+
"papermill": {
|
|
932
|
+
"duration": 0.007026,
|
|
933
|
+
"end_time": "2026-02-02T13:03:51.555187",
|
|
934
|
+
"exception": false,
|
|
935
|
+
"start_time": "2026-02-02T13:03:51.548161",
|
|
936
|
+
"status": "completed"
|
|
937
|
+
},
|
|
938
|
+
"tags": []
|
|
939
|
+
},
|
|
940
|
+
"source": [
|
|
941
|
+
"> **Save Reminder:** Save this notebook (Ctrl+S / Cmd+S) before running the next one.\n",
|
|
942
|
+
"> The next notebook will automatically export this notebook's HTML documentation from the saved file."
|
|
943
|
+
]
|
|
944
|
+
}
|
|
945
|
+
],
|
|
946
|
+
"metadata": {
|
|
947
|
+
"kernelspec": {
|
|
948
|
+
"display_name": "Python 3",
|
|
949
|
+
"language": "python",
|
|
950
|
+
"name": "python3"
|
|
951
|
+
},
|
|
952
|
+
"language_info": {
|
|
953
|
+
"codemirror_mode": {
|
|
954
|
+
"name": "ipython",
|
|
955
|
+
"version": 3
|
|
956
|
+
},
|
|
957
|
+
"file_extension": ".py",
|
|
958
|
+
"mimetype": "text/x-python",
|
|
959
|
+
"name": "python",
|
|
960
|
+
"nbconvert_exporter": "python",
|
|
961
|
+
"pygments_lexer": "ipython3",
|
|
962
|
+
"version": "3.12.4"
|
|
963
|
+
},
|
|
964
|
+
"papermill": {
|
|
965
|
+
"default_parameters": {},
|
|
966
|
+
"duration": 13.137985,
|
|
967
|
+
"end_time": "2026-02-02T13:03:54.179035",
|
|
968
|
+
"environment_variables": {},
|
|
969
|
+
"exception": null,
|
|
970
|
+
"input_path": "/Users/Vital/python/CustomerRetention/exploration_notebooks/08_baseline_experiments.ipynb",
|
|
971
|
+
"output_path": "/Users/Vital/python/CustomerRetention/exploration_notebooks/08_baseline_experiments.ipynb",
|
|
972
|
+
"parameters": {},
|
|
973
|
+
"start_time": "2026-02-02T13:03:41.041050",
|
|
974
|
+
"version": "2.6.0"
|
|
975
|
+
}
|
|
976
|
+
},
|
|
977
|
+
"nbformat": 4,
|
|
978
|
+
"nbformat_minor": 5
|
|
979
|
+
}
|