<jupyter_start><jupyter_text>Predicción del Customer Churn***¿Qué es el Customer Churn o Abandono del cliente?***Es una de las métricas más importantes que debe evaluar una empresa en *crecimiento*. Esta métrica puede darle a los directivos un acercamiento a la realidad, sobre el comportamiento en la retención de sus clientes. Es difícil medir el éxito si no se miden también los inevitables fracasos. Si bien, se hace un esfuerzo por retener a los clientes, no siempre es posible sin realizar un estudio y seguimiento apropiado. Es ahí donde el concepto de la rotación de clientes entra a jugar un papel fundamental.**Existe distintas clasificación de Churn:**- Churn Voluntario: el cliente abandona de manera voluntaria la compra/uso del producto o servicio.- Churn Involuntario: la empresa le suspende al cliente el suministro del producto o servicio.- Churn No contractual: el cliente no está obligado a guardar relación con la empresa a través de un contrato, sin embargo abandona la compra/uso del producto o servicio.**Algunas causas del Churn voluntario:**- Falta de uso- Servicio/producto deficiente- Mejor precio/calidad/disponibilidad en otro producto/servicio**Algunos ejemplos de abandono de clientes (Churn):**- El cliente compra en una tienda diferente- El cliente deja de adquirir un servicio o producto- El cliente cancela un servicio que está bajo contrato- Caducidad de la tarjeta de crédito<jupyter_code># Importar librerias
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
# Configuración de visualización de dataframes
pd.set_option("display.max_columns", 100)
pd.set_option("display.max_rows", None)<jupyter_output><empty_output><jupyter_text>Carga de datos<jupyter_code>telco = pd.read_csv('data/Telco_Churn.csv')
telco.head()<jupyter_output><empty_output><jupyter_text>Primero revisaremos la estructura de nuestro conjunto de datos de clientes, que se ha precargado en un DataFrame llamado Telco. *Poder verificar la estructura de los datos es un paso fundamental en el proceso de modelado de abandono.*<jupyter_code>telco.info()
telco.columns<jupyter_output><empty_output><jupyter_text>Descripción de columnas **Contenido del Dataset:**Cada fila representa un cliente, cada columna contiene los atributos del cliente, presentes en el último mes. El conjunto de datos incluye información sobre: - 'Churn': Yes/No; el cliente SI/NO se fue con otro proveedor de servicio- 'Account_Length': Número de meses desde que el cliente se suscribió- 'Vmail_Message': Número de mensajes de texto enviados- 'Day_Mins': Minutos promedios consumidos en el día- 'Eve_Mins': Minutos promedios consumidos en la tarde- 'Night_Mins': Minutos promedios consumidos en la noche- 'Intl_Mins': Minutos promedios en llamadas internacionales consumidos en el día- 'CustServ_Calls': Número de llamadas realizadas a servicio al cliente- 'Intl_Plan': Yes/No posee servicio de llamadas internacionales- 'Vmail_Plan': Yes/No posee servicio de mensaje de voz- 'Day_Calls': Promedio de Llamadas realizadas en el día- 'Day_Charge': Monto promedio de carga en doláres en llamadas diarias- 'Eve_Calls': Promedio de Llamadas realizadas en la tarde- 'Eve_Charge': Monto promedio de carga en doláres en llamadas en la tarde- 'Night_Calls': Promedio de Llamadas realizadas en la noche- 'Night_Charge': Monto promedio de carga en doláres en llamadas nocturnas- 'Intl_Calls': Promedio de Llamadas realizadas en el día- 'Intl_Charge': Monto promedio de carga en doláres en llamadas internacionales- 'State': Código de Estado del país (Estados Unidos)- 'Area_Code': código de área telefónica- 'Phone': Número de teléfono  Caracterizacón del problema- Clasificación binaria- Tipo desbalanceadoLa característica de particular interés en el problema o target, es el `Churn`, que puede tomar dos valores (yes/no) que indican si el cliente ha abandonado o no.     Exploremos la feature -Churn-, para contestar a la pregunta: ¿Cuántos churn/no churn tiene el conjunto de datos?<jupyter_code>freq = telco.Churn.value_counts()
print(f"\tCantidad de No Churn/Churn:\n{freq}\n\n  % de No Churn/Churn:\n{round(freq/len(telco)*100, 2)}\n\n")
ax = freq.plot.barh()
for x, y, c, l in zip(freq.values-200, [0.01, 0.99], np.array(freq/len(telco)*100), ["top", 'right']):
    ax.text(x, y, str(round(c, 2)) + '%', ha='center', va='center', color='w', weight='bold')
    ax.spines[l].set_visible(False)
plt.title('Churn: Distribución de clases')
# ax.set_frame_on(False)
plt.show()<jupyter_output><empty_output><jupyter_text>--> A simple vista podemos apreciar que se trata de un caso clases binarias desbalanceadas -> la balanza se inclina hacia una de las categorias ;)  Análisis Exploratorio de los datosSe explora la variable target `Churn`, para ver si hay diferencias en los atributos, entre quiénes abandonan y no abandonan.Nos podemos hacer preguntas interesantes como:        ¿Un cliente que abandona...- realiza más llamadas a servicio al cliente?- disminuye el consumo de llamadas?- realiza cargas de menor monto?- se ubican en determinada región geográfica?Podemos plantear las siguientes***Hipótesis:***1. Los clientes que abandonan realizan más llamadas de servicio al cliente1. Los clientes que abandonan realizan menos envíos de mensajes de texto1. Los clientes que abandonan realizan más llamadas en el día1. Los clientes que abandonan realizan más llamadas en la tarde1. Los clientes que abandonan cargan un monto mayor a su cuenta1. Los clientes que abandonan y tienen planes internacionales realizan más llamadas de servicio al cliente1. Los clientes que abandonan y tienen planes de correo de voz realizan más llamadas de servicio al clienteAhora tratemos de corroborar estas hipótesis: --> Visualización de los valores medios de distintas features agrupados por NO_CHURN/CHURN<jupyter_code>promedios = round(telco.drop('Area_Code', axis=1).groupby(['Churn']).mean(), 2)
promedios
valores_medianos = round(telco.drop('Area_Code', axis=1).groupby(['Churn']).median(), 2)
valores_medianos
promedios.T.plot.bar(figsize=(16, 6))
plt.title('Valores promedios de las variables - Grupos Churn/No Churn', fontsize=14)
plt.show()
# División de tipos de variables continuas y enteras para realizar los análisis exploratorios
var_continuas = list(telco.select_dtypes(include=['float64']).columns)

var_discretas = list(telco.select_dtypes(include=['int64']).columns)
var_discretas.remove('Area_Code')

print(f"Variables continuas:\n {var_continuas}\n\nVariables discretas:\n {var_discretas}")<jupyter_output><empty_output><jupyter_text>Variables continuas --> Visualicemos las distribuciones de las features de tipo continuas:<jupyter_code>features = var_continuas
fig = plt.figure(figsize=(15, 10))
for i, feature in enumerate(features):
    ax = plt.subplot(int(np.ceil(len(features)/3)), 3, i+1)
    sns.histplot(telco[feature], ax=ax, kde=True)
    plt.tight_layout()
fig = plt.figure(figsize=(15, 10))
features = var_continuas
rows = int(np.ceil(len(features)/3))
for i, feature in enumerate(features):
    ax = plt.subplot(rows, 3, i+1)
    sns.histplot(telco[telco['Churn']=='no'][feature], ax=ax, kde=True, color='green')
    sns.histplot(telco[telco['Churn']=='yes'][feature], ax=ax, kde=True, color='red')
    plt.tight_layout()<jupyter_output><empty_output><jupyter_text>Variables Discretas<jupyter_code>fig = plt.figure(figsize=(15, 10))
features = var_discretas
rows = int(np.ceil(len(features)/3))
for i, feature in enumerate(features):
    ax = plt.subplot(rows, 3, i+1)
    sns.countplot(x = feature, data = telco, ax=ax, hue='Churn')
    plt.tight_layout()<jupyter_output><empty_output><jupyter_text>Se observa ciertas diferencias en las medias de los valores de:- Vmail_Message- Day_Mins- Eve_Mins- CustServ_Calls- Day_Charge... exploraremos un poco más los datos para ver qué patrones conseguimos en estos.  => 1) Ha: *Los clientes que abandonan realizan más llamadas a servicio al cliente*<jupyter_code>fig, axs = plt.subplots(1, 2, figsize=(12, 4))
sns.boxplot(x = 'Churn', y = 'CustServ_Calls', data = telco, ax=axs[0])
axs[0].set_title('Distribución de la cant de llamadas \na servicio al cliente')
sns.boxplot(x = 'Churn', y = 'CustServ_Calls', data = telco, sym='', ax=axs[1])
axs[1].set_title('Distribución de la cant de llamadas \na servicio al cliente (sin outliers)')
plt.show()<jupyter_output><empty_output><jupyter_text>=> 2) Ha: *Los clientes que abandonan realizan menos envíos de mensajes de texto*<jupyter_code>sns.boxplot(x = 'Churn', y = 'Vmail_Message', data = telco)
plt.show()<jupyter_output><empty_output><jupyter_text>Más exploración a fondo... --> Exploremos con más detalle la feature `Churn` para ver si se encuentran algunas diferencias entre quienes abandonan y no abandonan.<jupyter_code># Frecuencia o Cantidad por número de llamadas a Servicio al cliente
df_Freq = pd.crosstab(telco['CustServ_Calls'], telco['Churn'], normalize=False, margins=True)
print(f"Cantidad de clientes: para Número de llamadas a servicio al cliente mensuales vs Churn/No Churn:\n{'-'*35}\n{df_Freq}")<jupyter_output><empty_output><jupyter_text>¿Cómo se interpreta esto? 76 clientes con perfil de abandono realizaron 4 llamadas mensuales al servicio al cliente<jupyter_code># Porcentajes del total de llamadas por Número de llamadas a Servicio al cliente
df_FreqRel = round(pd.crosstab(telco['CustServ_Calls'], telco['Churn'], normalize=True, margins=True), 4)*100
print(f"Porcentaje de clientes (del total): para Número de llamadas a servicio al cliente mensuales vs Churn/No Churn:\n{'-'*40}\n{df_FreqRel}")<jupyter_output><empty_output><jupyter_text>¿Cómo se interpreta esto? 2,28% de los clientes con perfil de abandono del total de clientes, realizaron 4 llamadas mensuales al servicio al cliente<jupyter_code># Probabilidades condicionadas al comportamiento Churn/No Churn (expresadas en porcentajes)
table = pd.crosstab(telco['CustServ_Calls'], telco['Churn'], normalize=True, margins=True)
table_ProbCond = round(table/table.loc['All',], 4)*100
print(f"Probabilidad Condicional: para Número de llamadas a servicio al cliente mensuales vs Churn/No Churn:\n{'-'*40}\n{table_ProbCond}")<jupyter_output><empty_output><jupyter_text>Pero...¿Cómo se interpreta esto? 15,73% de los clientes con perfil de abandono realizaron 4 llamadas mensuales al servicio al cliente   => 3) Ha: *Los clientes que abandonan realizan más llamadas en el día*<jupyter_code>sns.boxplot(x = 'Churn', y = 'CustServ_Calls', data = telco)
plt.show()<jupyter_output><empty_output><jupyter_text>=> 4) Ha: *Los clientes que abandonan realizan más llamadas en la tarde*<jupyter_code>sns.boxplot(x = 'Churn', y = 'Eve_Mins', data = telco)
plt.show()
telco.groupby(['Churn'])['Eve_Mins'].describe()<jupyter_output><empty_output><jupyter_text>=> 5) Ha: *Los clientes que abandonan cargan un monto mayor a su cuenta*<jupyter_code>sns.boxplot(x = 'Churn', y = 'Night_Mins', data = telco)
plt.show()
telco.groupby(['Churn'])['Night_Mins'].describe()<jupyter_output><empty_output><jupyter_text>=> 6) Ha: *Los clientes que abandonan y tienen planes internacionales realizan más llamadas de servicio al cliente*<jupyter_code>sns.boxplot(x = 'Churn', y = 'CustServ_Calls', data = telco, hue = "Intl_Plan")
plt.show()
telco.groupby(['Churn', 'Intl_Plan'])['CustServ_Calls'].describe()<jupyter_output><empty_output><jupyter_text>=> 7) Ha: *Los clientes que abandonan y tienen planes de correo de voz realizan más llamadas de servicio al cliente*<jupyter_code>sns.boxplot(x = 'Churn', y = 'CustServ_Calls', data = telco, hue = "Vmail_Plan")
plt.show()
telco.groupby(['Churn', 'Vmail_Plan'])['CustServ_Calls'].describe()<jupyter_output><empty_output><jupyter_text>**Preparación de datos**Luego de haber realizado un análisis exploratorio y teniendo una mejor comprensión del conjunto de datos, es necesario realizar un preprocesamiento de los datos para prepararlos para la fase de modelado. Para ello, se deben tener algunas consideraciones:    - Supuestos del modelo:    Muchos modelos de aprendizaje automático hacen ciertas suposiciones sobre cómo se distribuyen los datos. Si las características del conjunto de datos no cumplen con estos supuestos, los resultados de los modelos no serán confiables. Es por eso que la etapa de preprocesamiento de datos es tan crítica.    - Tipos de datos:    La mayoría de modelos de aprendizaje automático solo aceptan tipos de datos numéricos. Por esto, las variables categóricas se deben codificar. Las variables categóricas nominales y categóricas ordinales se codifican de manera distinta.    Variables nominales --> One Hot EncodingVariables dictómicas, binarias o Bernoulli --> Label Encoding/Diccionario/np.where()  Feature Engineering: Codificación de variables  Codificación de variables dicotómicas:Una *variable dicotómica* es aquella que toma uno de los dos únicos valores posibles, cuando se observa o mide.<jupyter_code># Columnas de datos tipo object
telco.select_dtypes('object').columns
binaries_var = ['Churn', 'Intl_Plan', 'Vmail_Plan']
print(f"Variables dicotómicas antes de codificar:\n{telco[binaries_var].head(3)}")

# Diccionario para realizar la codificación
dict_bin = {'no': 0, 'yes': 1}

# Variables dicotómicas a codificar
binaries_var = ['Churn', 'Intl_Plan', 'Vmail_Plan']

# Codificación
classes_bin = telco[binaries_var].apply(lambda x: x.replace(dict_bin))
print(f"\n\nVariables dicotómicas codificadas:\n{classes_bin.head(3)}")<jupyter_output><empty_output><jupyter_text>Si necesitas enfocarnos más en una categoría de la variables, podríamos usar este modo de codificación:<jupyter_code># Creamos un nuevo dataframe para preparar los datos para el modelo
# Se elimina la columna de Estado, no vamos a trabajar con ella para simplificar el análisis
# Se elimina la columna de los números telefónicas, es una variable de valores únicos y no aporta información
# De igual manera sucede con la columna del código de área
modeling_data = telco.drop(columns=['State', 'Phone', 'Area_Code']).copy()
modeling_data['Churn'] = np.where(modeling_data['Churn']=='yes', 1, 0)
modeling_data['Intl_Plan'] = np.where(modeling_data['Intl_Plan']=='no', 1, 0)
modeling_data['Vmail_Plan'] = np.where(modeling_data['Vmail_Plan']=='no', 1, 0)

# Veamos cómo quedan estas columnas
modeling_data[binaries_var].head()<jupyter_output><empty_output><jupyter_text>Label Encoding[LabelEncoder Sklearn](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.LabelEncoder.html)<jupyter_code>from sklearn.preprocessing import LabelEncoder

# Ejemplo de Label Encoding

# Instanciar LabelEncoder
le = LabelEncoder()

# Ajuste los datos
le.fit(["paris", "paris", "tokyo", "amsterdam"])

# Etiquetas de clases encontradas
print(f"Clases encontradas: {list(le.classes_)}\n")

# Lista de categorias a codificar
cities = ["tokyo", "tokyo", "paris", 'amsterdam']

# Codificación de la lista de categorias
cod_cities = list(le.transform(cities))
print(f'De Categorias a códigos: {cities} --> {cod_cities}')

inv_labels = list(le.inverse_transform(cod_cities))
print(f'De códigos a categorias: {cod_cities} --> {inv_labels}')
# Instanciar LabelEncoder
le = LabelEncoder()

# Lista de clases de las variables
classes = telco['Churn'].unique().tolist()

# Ajuste a las clases
le.fit(classes)

# Aplicar la codificación a cada una de las columnas
classes_bin_label = telco[binaries_var].apply(lambda x: list(le.transform(x)))

# Resultado de la codificación
classes_bin_label.head(3)<jupyter_output><empty_output><jupyter_text>One-Hot Encoding La dicotomización o binarización consiste en tratar datos continuos (previamente categorizados) o variables politómicas como si fueran variables binarias.**Implicaciones de utilizar [`get_dummies()`](https://pandas.pydata.org/docs/reference/api/pandas.get_dummies.html) de pandas y [`OneHotEncoder`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.OneHotEncoder.htmlsklearn.preprocessing.OneHotEncoder) de sklearn:**OneHotEncoder de sklearn guarda las categorías ajustadas para su posterior transformación, lo que es extremadamente útil cuando se requiere aplicar el mismo preprocesamiento de datos en un nueo conjunto de datos.--> Codificación de features politómicas (Dicotomización):<jupyter_code>bin_labels_state = pd.get_dummies(telco['State'], drop_first=False)
print(bin_labels_state.shape)
bin_labels_state.head()
bin_labels_state = pd.get_dummies(telco['State'], drop_first=True)
print(bin_labels_state.shape)
bin_labels_state.head()
from sklearn.preprocessing import OneHotEncoder

ohe = OneHotEncoder()
ohe.fit(telco[['State']])

bin_labels_state = ohe.transform(telco[['State']])
bin_labels_state = pd.DataFrame(bin_labels_state.toarray(), columns=ohe.categories_, dtype='int')
print(bin_labels_state.shape)
bin_labels_state.head()<jupyter_output><empty_output><jupyter_text>Feature ScalingAlgunos algoritmos son muy sensibles a características que abarcan diversos grados de magnitud, rango y unidades. Aquí está lo curioso del escalado de funciones: mejora (significativamente) el rendimiento de algunos algoritmos de aprendizaje automático y no funciona en absoluto para otros. ¿Cuál podría ser la razón detrás de esta peculiaridad?Los algoritmos de aprendizaje automático como la `regresión lineal`, la `regresión logística`, la `red neuronal`, que utilizan el descenso de gradiente como técnica de optimización, requieren escalar los datos.El escalado de datos garantiza que el descenso del gradiente se mueva suavemente hacia los mínimos y que los pasos del descenso del gradiente se actualicen al mismo ritmo para todas las entidades.Tener características en una escala similar puede ayudar a que el descenso del gradiente converja más rápidamente hacia los mínimos.Los algoritmos de distancia como `KNN`, `K-Means` y `SVM` son los más afectados por el rango amplios de las variables. Esto se debe a que estos utilizan distancias entre puntos de datos para determinar su similitud.Existe la posibilidad de que se otorgue un mayor peso a las características con mayor magnitud. Esto afectará el rendimiento del algoritmo y, que esté sesgado hacia una característica.El escalado permite que todas las características contribuyan por igual al resultado.Los algoritmos basados en `Árboles de Decisión`, son bastante insensibles a la escala de las características. Un árbol de decisiones solo divide un nodo en función de una única característica. El árbol de decisión divide un nodo en una característica que aumenta la homogeneidad del nodo. Esta división de una característica no se ve influenciada por otras características.**Técnicas de Escalado:**La `normalización` es una técnica de escalado en la que los valores se cambian y se vuelven a escalar para que terminen oscilando entre 0 y 1. También se conoce como escalado Min-Max ([Sklearn MinMaxScaler](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.MinMaxScaler.htmlsklearn.preprocessing.MinMaxScaler) ).La `estandarización` es otra técnica de escalado donde los valores se centran alrededor de la media con una desviación estándar unitaria. La media del atributo es cero y la distribución resultante tiene una desviación estándar de 1 ([Sklearn StandardScaler](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.StandardScaler.htmlsklearn.preprocessing.StandardScaler)).¿Cuál es la diferencia entre normalización y estandarización? Estas son dos de las técnicas de escalado de características más utilizadas en el aprendizaje automático, pero existe un nivel de ambigüedad en su comprensión. ¿Cuándo deberías usar qué técnica?                    NORMALIZACIÓN                        ESTANDARIZACIÓN      **¿Normalización o Estandarización?**- *Normalización*: Cuando sabe que la distribución de sus datos no sigue una distribución gaussiana. Puede ser útil en algoritmos que no asumen ninguna distribución de los datos como `KNN` y `Redes Neuronales`.- *Estandarización*: puede ser útil cuando los datos siguen una distribución gaussiana (no es una regla). A diferencia de la normalización, la estandarización no tiene un rango límite. Sí tiene valores atípicos en sus datos, no se verán afectados por la estandarización.La elección de utilizar la normalización o la estandarización dependerá de su problema y del algoritmo de aprendizaje automático que esté utilizando. No existe una regla estricta y rápida que le indique cuándo normalizar o estandarizar sus datos. Siempre puede comenzar ajustando su modelo a datos sin procesar, normalizados y estandarizados y comparar el rendimiento para obtener los mejores resultados.<jupyter_code>from sklearn.preprocessing import StandardScaler

# Instanciar StandardScaler
scaler = StandardScaler()

# Selección de variables para escalar (tomamos las variables continuas)
select_vars = var_continuas

# Medidas de Resumen Estadístico de variables antes de escalar
summary = round(telco[select_vars[:4]].describe(), 3)
print(f"Medidas de Resumen Estadístico de variables a escalar:\n\n{summary}")

# modeling_data[select_vars] 
modeling_data[select_vars] = scaler.fit_transform(modeling_data[select_vars])

# Resumen de medidas estadísticas post escalado
std_summary = round(modeling_data[select_vars[:4]].describe(), 3)
print(f"\n\nMedidas de Resumen Estadístico de variables estandarizadas:\n\n{std_summary}")
from sklearn.preprocessing import MinMaxScaler

# Instanciar StandardScaler
scaler = MinMaxScaler()

# Selección de variables para escalar (tomamos las variables continuas)
select_vars = var_continuas

# Medidas de Resumen Estadístico de variables antes de escalar
summary = round(telco[select_vars[:4]].describe(), 3)
print(f"Medidas de Resumen Estadístico de variables a escalar:\n\n{summary}")

# modeling_data[select_vars] 
var_norm = scaler.fit_transform(modeling_data[select_vars])
var_norm = pd.DataFrame(var_norm, columns=select_vars)

# Resumen de medidas estadísticas post escalado
std_summary = round(var_norm[select_vars[:4]].describe(), 3)
print(f"\n\nMedidas de Resumen Estadístico de variables estandarizadas:\n\n{std_summary}")
features = ['Day_Mins', 'Intl_Mins']

fig = plt.figure(figsize=(10, 4))
for i, feature in enumerate(features):
    ax = plt.subplot(1, 2, i+1)
    sns.histplot(x = feature, data = telco, ax=ax, hue='Churn')
    plt.suptitle('Antes de Estandarización')
    plt.tight_layout()
    
fig = plt.figure(figsize=(10, 4))
for i, feature in enumerate(features):
    ax = plt.subplot(1, 2, i+1)
    sns.histplot(x = feature, data = modeling_data, ax=ax, hue='Churn')
    plt.suptitle('Post-Estandarización')
    plt.tight_layout()

var_norm['Churn'] = telco['Churn']
fig = plt.figure(figsize=(10, 4))
for i, feature in enumerate(features):
    ax = plt.subplot(1, 2, i+1)
    sns.histplot(x = feature, data = var_norm, ax=ax, hue='Churn')
    plt.suptitle('Post-Normalización')
    plt.tight_layout()<jupyter_output><empty_output><jupyter_text>Ajuste del Modelo y predicción de datos<jupyter_code># Selección de variables categorias
variable_excluir = ['Churn', 'State', 'Phone', 'Area_Code']
var_categoricas = list(telco.select_dtypes(include='object').drop(columns=variable_excluir, errors='ignore').columns)
var_categoricas
# Selección de variables predictoras
X_columns = telco.select_dtypes(exclude='object').drop(columns=variable_excluir, errors='ignore').columns
X_columns
# Armado de dataframes para modelar (para probar un modelo con datos sin escalar)
data_X = pd.concat([modeling_data[var_categoricas], telco[X_columns]], axis=1)
data_y = modeling_data[['Churn']]
data_X.head()<jupyter_output><empty_output><jupyter_text>Crear conjuntos de entrenamiento y de pruebaEl tamaño de sus conjuntos de entrenamiento y prueba influye en el rendimiento del modelo. Los modelos aprenden mejor cuando tienen más datos de entrenamiento. Sin embargo, existe el riesgo de que se ajusten demasiado a los datos de entrenamiento y no generalice bien respecto a los datos nuevos; por lo que para evaluar correctamente la capacidad del modelo para generalizar, necesita suficientes datos de prueba. Como resultado, existe un importante equilibrio y compensación entre la cantidad que usa para el entrenamiento y la cantidad que tiene para las pruebas.Para grandes cantidades de instancias de datos, bastará con un porcentaje pequeño para el conjunto de test.Ver: [train_test_split](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.train_test_split.html)<jupyter_code>from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(data_X, data_y, test_size=0.3, random_state=42)
print(X_train.shape)
X_train.head()
print(y_train.shape)
y_train.head()<jupyter_output><empty_output><jupyter_text>Modelo de Regresión Logistica[LogisticRegression](https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LogisticRegression.html)<jupyter_code>from sklearn.linear_model import LogisticRegression

# Instanciar el clasificador
clf_LR = LogisticRegression(solver='liblinear', penalty='l1')

# Ajustar el modelo de clasificación
clf_LR.fit(X_train, y_train.values.ravel())

# Calcular las predicciones
y_pred = clf_LR.predict(X_test)

# Calcular accuracy
print('Accuracy en test:', clf_LR.score(X_test, y_test), end='\n\n')<jupyter_output><empty_output><jupyter_text>Modelo de Random Forest[RandomForestClassifier](https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestClassifier.htmlsklearn.ensemble.RandomForestClassifier)<jupyter_code>from sklearn.ensemble import RandomForestClassifier

# Instanciar el clasificador
clf_RF = RandomForestClassifier(random_state=28)  # class_weight='balanced_subsample'

# Ajustar el modelo de clasificación a los datos de entrenamiento
clf_RF.fit(X_train, y_train.values.ravel())

# Calcular las predicciones
y_pred = clf_RF.predict(X_test)

# Calcular accuracy
print('Accuracy en test:', clf_RF.score(X_test, y_test), end='\n\n')<jupyter_output><empty_output><jupyter_text>Matriz de Confusión$$ $$$$ $$$$Accuracy=\frac{\Sigma (TP + TN)}{\Sigma(TP + FP + TN + FN)}$$$$Precision=\frac{\Sigma TP}{\Sigma(TP + FP)}$$$$Recall=\frac{\Sigma TP}{\Sigma(TP + FN)}$$ ---- PRECISION: de las etiquetas positivas predichas, qué porcentaje es predicha correctamente.- RECALL: de las etiquetas positivas reales, qué porcentaje es predicha correctamente.- ESPECIFICIDAD: de las etiquetas negativas reales, qué porcentaje es predicha correctamente.<jupyter_code>from sklearn.metrics import confusion_matrix

# Veamos la matriz de c
conf_mat = confusion_matrix(y_test, y_pred)
tn, fp, fn, tp = conf_mat.ravel()
conf_mat = pd.DataFrame(conf_mat, columns=['No Churn', 'Churn'], index=['No Churn', 'Churn'])
sns.heatmap(conf_mat, annot=True, fmt='.2f', linewidths=1, cbar=False)
plt.xlabel('Predicted Label', fontsize=12)
plt.ylabel('Actual Label', fontsize=12)
plt.title('Confusion Matrix', fontdict={'fontsize':14, 'color':'blue', 'fontweight':'bold'})
plt.show()
# Print the confusion matrix (porcentual values)
conf_mat = confusion_matrix(y_test, y_pred, normalize='all')*100
conf_mat = pd.DataFrame(conf_mat, columns=['No Churn', 'Churn'], index=['No Churn', 'Churn'])
sns.heatmap(conf_mat, annot=True, fmt='.2f', linewidths=1, cbar=False)
plt.xlabel('Predicted Label', fontsize=12)
plt.ylabel('Actual Label', fontsize=12)
plt.title('Confusion Matrix', fontdict={'fontsize':14, 'color':'blue', 'fontweight':'bold'})
plt.show()<jupyter_output><empty_output><jupyter_text>--> Resumiendo las Métricas de Clasificación, en el contexto del problema que se está abordando:<jupyter_code>print(f'- Número de etiquetas reales positivas (Churn): {sum(y_test["Churn"]==1)}')
print(f'- Número de etiquetas reales negativas (No Churn): {sum(y_test["Churn"]==0)}\n')
print(f'- El clasificador tuvo {tp + tn} ({tp} + {tn}) predicciones correctas')
print(f'- {tp} de las predicciones son verdaderos positivos (TP)')
print(f'- {tn} de las predicciones son verdaderos negativos (TN)')
print(f'- {fp} de las predicciones son falsos positivos (FP)')
print(f'- {fn} de las predicciones son falsos negativos (FN)')
print(f'- La exactitud (accuracy) es {round((tp + tn)/(tp + tn + fp + fn)*100, 2)} % = ({tp} + {tn})/({tp} + {tn} + {fp} + {fn})*100')
print(f'- La precisión es de {round(tp/(tp + fp)*100, 2)} = ({tp})/({tp} + {fp})*100.')
print(f'- La sensibilidad (recall) es de {round(tp/(tp + fn)*100, 2)} = ({tp})/({tp} + {fn})*100.')<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10135635018495684, 'fraction_numerical': 0.006799365862251189, 'mean_word_length': 6.297172236503856, 'pattern_counts': {'":': 0, '<': 113, '<?xml version=': 0, '>': 131, 'https://': 8, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '28', 'dir': 'jupyter-structured-clean-dedup', 'id': '331663', 'license': 'MIT', 'n_tokens_mistral': 9874, 'n_tokens_neox': 9657, 'n_words': 2919, 'path': 'CLASSIFICATION/CustomerChurnTelco.ipynb', 'repo_name': 'ninargue/EstrategiasBigData', 'size': '28385'}