Aprendizaje automático para encontrar clientes potenciales

¿Qué es el aprendizaje automático no supervisado?

Bienvenido a mi tercer post sobre Data Science! En mi publicación anterior hablé sobre cómo utilicé el aprendizaje automático supervisado para encontrar donantes para una organización benéfica.

Recuerde que en el aprendizaje automático supervisado tiene variables de entrada (X) y una variable de salida (Y) y utiliza un algoritmo para aprender la función de mapeo de la entrada a la salida.

Por el contrario, en el aprendizaje automático no supervisado solo tiene datos de entrada (X) y no tiene variables de salida correspondientes.

El objetivo del aprendizaje no supervisado es modelar la estructura o distribución subyacente en los datos para aprender más sobre los datos.

Estos se llaman aprendizaje no supervisado porque, a diferencia del aprendizaje supervisado anterior, no hay respuestas correctas y no hay un maestro. Los algoritmos se dejan a sus propios dispositivos para descubrir y presentar la estructura interesante en los datos.

Los problemas de aprendizaje no supervisados ​​se pueden agrupar en problemas de agrupación y asociación.

Agrupación: un problema de agrupación es cuando desea descubrir las agrupaciones inherentes en los datos, como agrupar clientes según el comportamiento de compra.

Asociación: un problema de aprendizaje de la regla de asociación es cuando desea descubrir reglas que describan grandes porciones de sus datos, como las personas que compran X también tienden a comprar Y.

Aspectos destacados del proyecto

En el proyecto que describiré aquí. Utilicé datos financieros reales de Bertelsmann Arvato Analytics para una empresa de ventas por correo en Alemania.

Mi tarea fue identificar los segmentos de la población que forman el núcleo de la base de usuarios de esta empresa. En otras palabras, resolví un problema de agrupamiento.

En los próximos párrafos elaboraré los pasos que tomé para lograr mi objetivo. Al igual que en la mayoría de los problemas de aprendizaje automático, no importa lo complejos que sean sus algoritmos, los datos deben limpiarse primero. Así que aquí vamos:)

Procesamiento de datos

Hay cuatro conjuntos de datos relevantes para este caso:

  • Udacity_AZDIAS_Subset.csv: Datos demográficos para la población general de Alemania; 891211 personas (filas) x 85 funciones (columnas).
  • Udacity_CUSTOMERS_Subset.csv: Datos demográficos para clientes de una empresa de venta por correo; 191652 personas (filas) x 85 características (columnas).
  • Data_Dictionary.md: Archivo de información detallada sobre las características en los conjuntos de datos proporcionados.
  • AZDIAS_Feature_Summary.csv: Resumen de atributos de características para datos demográficos; 85 características (filas) x 4 columnas

Cada fila de los archivos demográficos representa a una sola persona, pero también incluye información fuera de los individuos, incluida información sobre su hogar, edificio y vecindario.

Utilicé estos datos para agrupar a la población general en grupos con propiedades demográficas similares. El propósito es ver cómo las personas en el conjunto de datos de los clientes encajan en estos grupos creados.

Veamos con qué características trabajaremos.

 # Cargue los datos demográficos generales. 
 azdias = pd.read_csv('Udacity_AZDIAS_Subset.csv', sep = ';') 

 # Cargue el archivo de resumen de características. 
 feat_info = pd.read_csv('AZDIAS_Feature_Summary.csv', sep = ';')
 print(feat_info)

En el archivo de diccionario, todas estas características se describen con más detalle. Es decir. se proporciona su significado en inglés, así como lo que significan los diferentes niveles de las características.

Observe que en la cuarta columna del resumen de atributos de la característica (cargado en feat_info arriba) documenta los códigos del diccionario de datos que indican que faltan o datos desconocidos.

Si bien el archivo codifica esto como una lista (por ejemplo, [-1,0]), se leerá como un objeto de cadena. He convertido los datos que coinciden con un & # 039; falta & # 039; o & # 039; desconocido & # 039; código de valor en un valor NaN numpy.

 # gire missing_or_unknown to list 
 feat_info ['missing_or_unknown'] = 
 feat_info['missing_or_unknown'].apply(lambda x: x[1:-1].split(',')) 

 # Identifique los valores de datos desconocidos o faltantes y conviértalos a NaNs. 
  for attrib, missing_values ​​ in zip(feat_info['attribute'],
 feat_info['missing_or_unknown']): 
     if missing_values​​[0] != '': 
         for value in missing_values: 
             if value.isnumeric() or value.lstrip('-').isnumeric(): 
                value = int(value) 
             azdias.loc[azdias[attrib] == value, attrib] = np.nan

Evalúe los datos faltantes en cada columna

 # Realice una evaluación de la cantidad de datos faltantes en cada columna del conjunto de datos 
 missing_data = pd.Series(azdias.isnull().sum() / len(azdias)) 
 missing_data.plot(kind = 'barh', figsize = (10,20))
 missing_data[missing_data > 0.2].index.tolist()
 ['AGER_TYP'
 'GEBURTSJAHR', 
 'TITEL_KZ', 
 'ALTER_HH', 
 'KK_KUNDENTYP'; 
 'KBA05_BAUMAX']
  # Eliminar las columnas de valores atípicos del conjunto de datos. (Más adelante realizarás otras tareas de ingeniería de datos, como la recodificación y la imputación). 
 azdias = 
 azdias.drop(['AGER_TYP','GEBURTSJAHR','TITEL_KZ','ALTER_HH',
              'KK_KUNDENTYP','KBA05_BAUMAX'] axis = 1)

He evaluado los datos que faltan utilizando un gráfico de barras. He clasificado como valores atípicos las columnas que tienen más del 20% como valores perdidos.

Como resultado encontré 6 columnas de este tipo que dejé caer. Otros valores atípicos que sobresalen son las características PLZ8 con valores perdidos del 13%; y las características de KBA05 con valores perdidos del 15%.

Evaluar datos faltantes en cada fila

Al igual que en el caso de evaluar datos faltantes en las columnas, busqué filas atípicas con datos faltantes.

Establecí el umbral como 10 valores perdidos por fila y dividí los datos en dos grupos: filas con alto número de valores perdidos (es decir, más de 10), y filas con bajo número de valores perdidos (es decir, más bajos que 10).

Comparé las distribuciones de varias características no perdidas entre estos dos grupos. El propósito era comprender si debería tratar estos puntos de datos de una manera específica.

 # ¿Cuántos datos faltan en cada fila del conjunto de datos? 
 missing_data_rows = azdias.isnull().sum(axis = 1)
 missing_data_rows_low = azdias[azdias.isnull().sum(axis = 1) < 10].reset_index(drop = True) 

 missing_data_rows_high = azdias[azdias.isnull().sum(axis = 1) >= 10].reset_index(drop = True) 

 def  countplot (columnas, núm): 
      fig, axs = plt.splotots(num, 2, figsize = (15, 15)) 
      fig.subplots_adjust(hspace = 2, wspace = .2) 
      axs = axs.ravel() 
      for i in range(num): 
         sns.countplot (missing_data_rows_low[columns[i]], ax = axs[i*2]) 
         axs[i*2].set_title('missing_data_rows_low') 
         sns.countplot(missing_data_rows_high[columns[i]], ax = axs [i*2+1]) 
         axs [i*2+1].set_title('missing_data_rows_high') 

 countplot(missing_data_rows_high.columns, 3)

Para algunas de las funciones que verifiqué, parece haber una distribución diferente entre las filas con muchos valores faltantes y pocos valores faltantes.

Esto implica que los datos son cualitativamente diferentes entre las filas con alto número de valores perdidos y bajo número de valores perdidos.

El grupo de filas con un número bajo de datos faltantes se considerará para un análisis adicional, mientras que el grupo de filas con un número alto de datos faltantes se considerará como un grupo adicional al final.

Data Wrangling

def clean_data(df):
"""
Perform feature trimming, re-encoding, and engineering for demographics
data

INPUT: Demographics DataFrame
OUTPUT: Trimmed and cleaned demographics DataFrame
"""

# Put in code here to execute all main cleaning steps:
# convert missing value codes into NaNs, ...

df_copy = df.copy()

# Identify missing or unknown data values and convert them to NaNs.
for col_name in df.columns:
df_copy[col_name] = df_copy[col_name].map(lambda x: np.nan if str(x) in feat_info.loc[col_name].missing_or_unknown else x)

# remove selected columns and rows, ...
c_removed =['AGER_TYP','GEBURTSJAHR','TITEL_KZ','ALTER_HH','KK_KUNDENTYP','KBA05_BAUMAX']

for c in c_removed:
df_copy.drop(c, axis=1, inplace=True)

df_copy = df_copy[df_copy.isnull().sum(axis=1) < 10].reset_index(drop=True)

for col in df_copy.columns:
df_copy[col] = df_copy[col].fillna(df_copy[col].mode()[0])


# select, re-encode, and engineer column values.
multi_level = []
for column in df_copy.columns:
if feat_info.loc[column].type == 'categorical' and len(df_copy[column].unique()) > 2:
multi_level.append(column)

for col in multi_level:
df_copy.drop(col, axis=1, inplace=True)

df_copy['decade'] = df_copy['PRAEGENDE_JUGENDJAHRE'].apply(create_interval_decade)
df_copy['movement'] = df_copy['PRAEGENDE_JUGENDJAHRE'].apply(create_binary_movement)
df_copy.drop('PRAEGENDE_JUGENDJAHRE', axis=1, inplace=True)

df_copy['wealth'] = df_copy['CAMEO_INTL_2015'].apply(wealth)
df_copy['life_stage'] = df_copy['CAMEO_INTL_2015'].apply(life_stage)
df_copy.drop('CAMEO_INTL_2015', axis=1, inplace=True)

df_copy = pd.get_dummies(data=df_copy, columns=['OST_WEST_KZ'])

mixed = ['LP_LEBENSPHASE_FEIN','LP_LEBENSPHASE_GROB','WOHNLAGE','PLZ8_BAUMAX']

for c in mixed:
df_copy.drop(c, axis=1, inplace=True)

# Return the cleaned dataframe.
return df_copy

Hice una función de datos de limpieza que podría aplicar tanto a los datos demográficos de la población general como a los datos demográficos del cliente.

Análisis de componentes principales

PCA es uno de los más utiliza herramientas de aprendizaje automático no supervisadas. Los componentes principales son ​​combinaciones lineales de las características originales en un conjunto de datos que tienen como objetivo retener la mayor cantidad de información en los datos originales.

El análisis de componentes principales es un método común para extraer nuevas “características latentes” de nuestro conjunto de datos, en función de las características existentes. Piense en un componente principal de la misma manera que piensa en una característica latente .

Cuando tenemos conjuntos de datos con cientos o miles de características, debemos reducir el número de Dimensiones para construir efectivamente un modelo. Hay dos métodos para hacer esto:

a) Selección de características: La selección de características implica encontrar un subconjunto de las características originales de sus datos que considere más relevantes y útiles.

b) Extracción de características: Extracción de características implica extraer o construir, nuevas características llamadas características latentes .

Transformación de características

Realicé características de escalado para que los vectores componentes principales no se vean influenciados por las diferencias naturales en la escala para las características.

 # Rellene los valores de Nan con el modo de esa columna respectiva. 
for col in missing_data_rows_low.columns:
missing_data_rows_low[col] = missing_data_rows_low[col].fillna(missing_data_rows_low[col].mode()[0])

# Aplicar escala de funciones a los datos demográficos de la población general.
normalizer = StandardScaler()
missing_data_rows_low[missing_data_rows_low.columns] = 
normalizer.fit_transform(missing_data_rows_low[missing_data_rows_low.columns])
missing_data_rows_low.head()

Reducción de dimensionalidad

# Apply PCA to the data.

pca = PCA()
missing_data_rows_low_pca = pca.fit_transform(missing_data_rows_low)
# Investigate the variance accounted for by each principal component.
def scree_plot(pca):
    '''
    Creates a scree plot associated with the principal components 
    INPUT: pca - the result of instantian of PCA in scikit learn
    OUTPUT:
    None
    '''
    
    num_components = len(pca.explained_variance_ratio_)
    ind = np.arange(num_components)
    vals = pca.explained_variance_ratio_

    plt.figure(figsize=(10, 6))
    ax = plt.subplot(111)
    cumvals = np.cumsum(vals)
    ax.bar(ind, vals)
    ax.plot(ind, cumvals)

    ax.xaxis.set_tick_params(width=0)
    ax.yaxis.set_tick_params(width=2, length=12)

    ax.set_xlabel("Principal Component")
    ax.set_ylabel("Variance Explained (%)")
    plt.title('Explained Variance Per Principal Component')

scree_plot(pca)

# Vuelva a aplicar PCA a los datos mientras selecciona el número de componentes a retener. 
pca = PCA(n_components=41)
missing_data_rows_low_pca = pca.fit_transform(missing_data_rows_low)

Según la gráfica de PCA, la variación explicada se vuelve extremadamente baja después de 41 componentes y no cambia después. Así que hice el PCA de nuevo con 41 componentes.

Cada componente principal es un vector unitario que apunta en la dirección de la varianza más alta (después de tener en cuenta la varianza capturada por los componentes principales anteriores).

Cuanto más lejos esté un peso de cero, mayor será el componente principal en la dirección de la característica correspondiente. Si dos características tienen grandes pesos del mismo signo (tanto positivo como negativo), los aumentos en uno tienden a estar asociados con aumentos en el otro.

Para contrastar, se puede esperar que las características con signos diferentes muestren una correlación negativa: los aumentos en una variable deberían dar como resultado una disminución en la otra.

Aplicar agrupamiento a la población general

def get_kmeans_score(data, center):
    '''
    returns the kmeans score regarding SSE for points to centers
    INPUT:
    data - the dataset you want to fit kmeans to
    center - the number of centers you want (the k value)
    OUTPUT:
    score - the SSE score for the kmeans model fit to the data
    '''
    #instantiate kmeans
    kmeans = KMeans(n_clusters=center)

    # Then fit the model to your data using the fit method
    model = kmeans.fit(data)

    # Obtain a score related to the model fit
    score = np.abs(model.score(data))

    return score
    # Over a number of different cluster counts...
    # run k-means clustering on the data and...
    # compute the average within-cluster distances.
    scores = []
    centers = list(range(1,30,3))

    for center in centers:
        scores.append(get_kmeans_score(missing_data_rows_low_pca, center))
    
    # Investigate the change in within-cluster distance across number of clusters.
    # HINT: Use matplotlib's plot function to visualize this relationship.
    plt.plot(centers, scores, linestyle='--', marker='o', color='b');
    plt.xlabel('K');
    plt.ylabel('SSE');
    plt.title('SSE vs. K')
# Vuelva a ajustar el modelo de k-means con el número seleccionado de conglomerados y obtenga predicciones 
# de conglomerados para los datos demográficos de la población general.

kmeans = KMeans(n_clusters=22)
model_general = kmeans.fit(missing_data_rows_low_pca)predict_general = model_general.predict(missing_data_rows_low_pca)

En la trama, podemos ver que 22 parece ser un número suficiente de grupos. Después, la tasa de cambio es SSE es extremadamente baja.

Compare los datos del cliente con los datos demográficos

Después de agrupar los datos demográficos de la población, aplicamos los mismos pasos de limpieza y agrupamiento a los datos demográficos de los clientes. El propósito es ver cuál es la sólida base de clientes de la empresa.

Si hay una mayor proporción de personas en un grupo para los datos del cliente en comparación con la población general (por ejemplo, el 5% de las personas se asigna a un grupo para la población general, pero el 15% de los datos de los clientes está más cerca del centroide de ese clúster), lo que sugiere que las personas de ese clúster sean el público objetivo de la empresa.

Por otro lado, la proporción de los datos en un grupo que es mayor en la población general que los datos de los clientes (por ejemplo, solo el 2% de los clientes más cercanos a un centroide de población que captura el 6% de los datos) sugiere que el grupo de personas sea

Analice un grupo en el que los datos del cliente están representados en exceso

# ¿Qué tipo de personas forman parte de un grupo que está sobrerrepresentado en los datos 
# de los clientes en comparación con la población general? 

over = normalizer.inverse_transform(
pca.inverse_transform(customers_clean_pca[np.where(predict_customers==11)])).round()
df_over = pd.DataFrame(data = over, columns = customers_clean.columns)
df_over.head(10)

Este segmento está compuesto por personas de entre 46 y 60 años que no son minimalistas financieros. Es decir. probablemente sean personas jubiladas o cercanas a él, que estén ansiosas por consumir bienes y servicios.

Analice un grupo en el que los datos de los clientes están poco representados

# ¿De qué tipo de personas forman parte? un grupo que está subrepresentado en los # datos del cliente en comparación con la población general? bajo = normalizer.inverse_transform (pca.inverse_transform (customers_clean_pca [np.where(predict_customers==16)]). round () df_under = pd.DataFrame (datos = debajo, columnas = clientes_clean.columnas) df_under.head (10)

Este segmento está compuesto por personas en el grupo de edad más joven (hasta 45 años), con una proporción mayor de personas desempleadas.

Conclusión del proyecto

Recuerde en el paso de procesamiento de datos que determinamos que el grupo de filas con un gran número de datos faltantes se considerará como un grupo adicional al final.

El grupo 22 en el grupo anterior es el último grupo agregado. podemos ver que hay una gran diferencia entre los porcentajes de población y los porcentajes de clientes en la mayoría de los grupos.

En la mayoría de los grupos, es la población general la que está sobre representada. Por ejemplo, en el grupo 11 donde el segmento de clientes está sobre representado; está compuesto por individuos en la edad de entre 46 y 60 años que no son minimalistas financieros.

Es decir. probablemente sean personas jubiladas o cercanas a él, que estén ansiosas por consumir bienes y servicios. Así que tiene sentido considerarlo un grupo de clientes.

En el otro lado, en el clúster 16, el segmento de clientes está sub representado. Este segmento está compuesto por personas en el grupo de edad más joven (hasta 45 años), con una mayor proporción de personas desempleadas.

Como la mayoría son desempleados, lo más probable es que vivan de la seguridad social. Por lo tanto, no tendrían grandes recursos desechables para gastar en productos que se les comercializan.

Palabras de cierre

Si llegas al final, muchas gracias por leer 🙂 Sé que estas publicaciones son largas e incluyen muchos códigos fragmentos, pero realmente creo que ignorar el aspecto técnico del aprendizaje automático no es posible para mostrar cómo funcionan los algoritmos.

Este proyecto fue gratificante para mí porque incluyó datos financieros del mundo real con un objetivo comercial del mundo real.

Espero que esto le permita comprender cómo se puede aplicar el aprendizaje automático no supervisado en los negocios y el marketing. Si desea leer todo el código que escribí, puede visitar mi proyecto en mi github .

Dejá un comentario