-
Notifications
You must be signed in to change notification settings - Fork 0
Modelo de ML
🇬🇧 English first · 🇪🇸 Español más abajo.
Classify subscription customers into churners and non-churners, and expose the weight of each factor so a retention team can act on the cause instead of the symptom.
flowchart TD
A["churn_data.csv"] --> B["pd.get_dummies on SubscriptionType"]
B --> C["9 features selected"]
C --> D["train_test_split · test_size=0.25 · stratify=y"]
D --> E["StandardScaler — fit on train, transform on test"]
E --> F["LogisticRegression(max_iter=1000, class_weight='balanced')"]
F --> G["predict + predict_proba"]
G --> H["classification_report · confusion_matrix · roc_auc_score"]
H --> I["JSON export to data/"]
from sklearn.linear_model import LogisticRegression
model = LogisticRegression(max_iter=1000, class_weight='balanced', random_state=42)
model.fit(X_train_s, y_train)There is no grid search and no cross-validation: a single fit with a fixed seed, so the run is reproducible.
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.25, random_state=42, stratify=y
)
scaler = StandardScaler()
X_train_s = scaler.fit_transform(X_train)
X_test_s = scaler.transform(X_test)Two details that are easy to get wrong and are done right here:
-
stratify=ykeeps the churn share identical in train and test. Without it, a random split can hand the test set a different class balance and the metrics stop being comparable. -
The scaler is fitted on train only.
fit_transformon train, plaintransformon test. Fitting on the whole dataset would leak test statistics into training.
| Metric | Where it comes from |
|---|---|
| Accuracy | classification_report(...)['accuracy'] |
| Precision, recall, F1 |
classification_report for class 1 (churn) |
| AUC-ROC |
roc_auc_score(y_test, y_proba) over predict_proba[:, 1]
|
| Confusion matrix |
confusion_matrix(y_test, y_pred), exported as tn/fp/fn/tp
|
Accuracy on its own says little here: the classes are uneven, and a model that never predicts churn already scores well. Recall on the churn class and the AUC-ROC are the ones that carry information.
The live figures are the ones published in the dashboard — see
Dashboard interactivo. Running python churn_analysis.py prints them to stdout
and rewrites the JSON files.
feature_importance = pd.Series(model.coef_[0], index=FEATURES).sort_values()A logistic regression has no feature_importances_. What it has is one coefficient
per feature, and because the features were standardised the coefficients are
comparable to each other. The sign is the part that matters: a positive
coefficient pushes towards churn, a negative one holds the customer.
That is why the chart in the dashboard is a diverging bar chart around zero, and
why the JSON field is called coefficient and not importance.
python generate_data.py # writes churn_data.csv
python churn_analysis.py # trains, prints metrics, writes data/*.jsonRequires pandas, numpy, scikit-learn, matplotlib and seaborn. Both scripts
fix random_state=42 / np.random.seed(42), so two runs on the same machine give
the same numbers.
Clasificar clientes de suscripción en cancelan y permanecen, y exponer el peso de cada factor para que un equipo de retención actúe sobre la causa y no sobre el síntoma.
Ver el diagrama de arriba. En texto: se convierte el tipo de suscripción en variables ficticias, se seleccionan 9 variables, se parte en 75/25 de forma estratificada, se estandariza ajustando solo con el tramo de entrenamiento, se entrena la regresión logística y se calculan las métricas.
from sklearn.linear_model import LogisticRegression
model = LogisticRegression(max_iter=1000, class_weight='balanced', random_state=42)
model.fit(X_train_s, y_train)No hay búsqueda de hiperparámetros ni validación cruzada: un único ajuste con semilla fija, para que la ejecución sea reproducible.
Dos detalles que es fácil hacer mal y que aquí están bien:
-
stratify=ymantiene idéntica la proporción de cancelaciones en entrenamiento y en prueba. Sin eso, una partición aleatoria puede dejar al tramo de prueba otro balance de clases y las métricas dejan de ser comparables. -
El escalador se ajusta solo con el entrenamiento.
fit_transformen train ytransforma secas en test. Ajustarlo sobre todo el conjunto filtraría al entrenamiento información del tramo de prueba.
| Métrica | De dónde sale |
|---|---|
| Acierto (accuracy) | classification_report(...)['accuracy'] |
| Precisión, recall, F1 |
classification_report para la clase 1 (cancela) |
| AUC-ROC |
roc_auc_score(y_test, y_proba) sobre predict_proba[:, 1]
|
| Matriz de confusión |
confusion_matrix(y_test, y_pred), exportada como tn/fp/fn/tp
|
El acierto por sí solo dice poco aquí: las clases no están igualadas y un modelo que no prediga nunca una cancelación ya puntúa bien. El recall sobre la clase que cancela y el AUC-ROC son los que llevan información.
Las cifras vigentes son las publicadas en el cuadro de mando — ver
Dashboard interactivo. Ejecutar python churn_analysis.py las imprime por
pantalla y reescribe los JSON.
Una regresión logística no tiene feature_importances_. Lo que tiene es un
coeficiente por variable y, como las variables se estandarizaron antes, los
coeficientes son comparables entre sí. Lo que importa es el signo: un
coeficiente positivo empuja hacia la cancelación, uno negativo retiene al cliente.
Por eso el gráfico del cuadro de mando es de barras divergentes alrededor del cero,
y por eso el campo del JSON se llama coefficient y no importance.
python generate_data.py # escribe churn_data.csv
python churn_analysis.py # entrena, imprime métricas y escribe data/*.jsonNecesita pandas, numpy, scikit-learn, matplotlib y seaborn. Los dos
scripts fijan random_state=42 / np.random.seed(42), así que dos ejecuciones en
la misma máquina dan los mismos números.