El siguiente artículo hace referencia al post que escribí en Datola. En él se recogen los pasos necesarios para poder llevar a cabo la segmentación de nuestros visitantes teniendo en cuenta parte de su comportamiento de compra, el cual como es lógico, se ha simplificado sobremanera precisamente para entender cada uno de los códigos Python propuestos. Para ello, vamos a utilizar los datos de GA4 de la cuenta Demo de Google almacenados en BigQuery. Antes de comenzar con la lectura propuesta, quizás sea recomendable leer este otro post primero: Algoritmos de clusterización y su impacto en el negocio.

.

 

Conexión a BigQuery

Como suele ser habitual en estas lides, el proceso se va a desarrollar a través de un Notebook Jupyter donde a través de celdas (código) y markdown (texto) se generará el storytelling del análisis. Como es lógico, para poder profundizar en las partes destacables y evitar un artículo tedioso, se van a obviar inserciones comunes como pueden ser las especificaciones iniciales o los análisis univariante y multivariante, entre otros. Por lo tanto, lo primero será añadir las librerías principales, las que prevemos esperar utilizar en breve.

# In [1]
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import warnings
warnings.filterwarnings('ignore')

Para poder acceder con Python a BigQuery debemos crear una cuenta de servicio con los permisos y el acceso a la API necesarios (más información) previamente configurados. Para crear la cuenta de servicio se debe acceder a en cloud.google.com ir a ‘IAM y administración’ y seguido a ‘cuentas de servicio’. Una vez ahí, tras crear la cuenta de servicio, dentro de ésta (pestaña ‘Claves’) clicar en ‘Agregar clave’ y ‘Crear clave nueva’. Solo queda seleccionar el tipo: JSON. Además, también deberemos activar la API de Bigquery. Para ello, ir a ‘APIs y servicios’ y clicar en ‘Habilitar APIs y servicios’. Buscar exactamente la denominada como ‘BigQuery API’ y clicar en ‘Habilitar’. Con estos dos sencillos pasos ya podríamos generar el código para acceder a los datos de GA4 almacenados en BigQuery. En este caso concreto, como se ve a continuación se ha utilizado la cuenta Demo de Google y más concretamente el conjunto de datos ‘ga4_obfuscated_sample_ecommerce’ (más información). Este dataset almacena datos de Google Merchandise Store, una tienda online que vende productos de la marca Google y que usa la implementación de comercio electrónico web estándar de Google Analytics 4 junto con la medición mejorada.

# In [2]

from google.oauth2 import service_account
from google.cloud import bigquery
from google.cloud.bigquery import ScalarQueryParameter

credentials=service_account.Credentials.from_service_account_file(
'C:/[RUTA]/[CUENTA DE SERVICIO].json'
)
client = bigquery.Client(credentials=credentials)

Creación de la consulta (query)

Una vez se establece la conexión, es el turno de generar la consulta para extraer los datos necesarios. En este caso, se ha llevado a cabo una query básica donde incluso se ha forzado la inserción de un valor “no útil” como es el caso del continente y cuya intención no es otra que la de explicar más tarde la utilidad de reducir la dimensionalidad.

# In [3]

start_date='20210130'
end_date='20210131'

query="""
SELECT
user_pseudo_id,
geo.continent,
geo.country,
ecommerce.purchase_revenue AS revenue,
count(distinct ecommerce.transaction_id) AS transactions,
ecommerce.total_item_quantity AS items
FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`
WHERE _TABLE_SUFFIX BETWEEN @START_DATE AND @END_DATE
GROUP BY 1,2,3,4,6
"""
query_params = [
ScalarQueryParameter('START_DATE', 'STRING', start_date),
ScalarQueryParameter('END_DATE', 'STRING', end_date)
]
job_config = bigquery.QueryJobConfig()
job_config.query_parameters = query_params
query = client.query(query, job_config=job_config)
results=query.result()

df=results.to_dataframe()

Normalmente se suele copiar o guardar el conjunto de datos con los valores extraídos con la consulta. Esto permite no llevar a cabo llamadas innecesarias (tiempo y gasto) en caso de tener que modificar el dataset después.

# In [4]

dfc = df.copy()
dfc.head(3)

# Out[4] ...
Resultado consulta Bigquery GA4

Resultado de la consulta Bigquery – GA4

Tratamiento de datos

Puesto que en este desarrollo la ausencia de datos solo se refiere a casos en los que no se han hecho compras con valores económicos, es decir aquellas columnas vinculadas a ingresos ([revenue] e [items]), solo será necesario sustituir los valores NaN por ceros. Además, para diferenciar transacciones “sin ingresos” (donde se han aplicado cupones 100%) y aportar algo más de valor a la segmentación final, se ha optado por añadir una nueva característica [buyer] con los valores: no buyer, no buyer coupon y buyer.

# In [5]

dfc['revenue']=dfc['revenue'].fillna(0)
dfc['items']=dfc['items'].fillna(0)

dfc['buyer'] = np.where(
(dfc['transactions'] > 0) & (dfc['revenue'] == 0.0),'no buyer coupon',np.where((dfc['transactions'] == 0) & (dfc['revenue'] == 0.0),'no buyer','buyer')
)

dfc.head(3)

# Out[5] ...
Data wrangling del dataframe

Data wrangling del dataframe

A partir de este momento, y aunque en cualquier otra circunstancia la dedicación a entender los datos y a corregirlos conllevaría un gran número de horas de trabajo, comienza la implementación del proyecto de segmentación de nuestros usuarios. En la mayoría de casos, cuando nos enfrentamos a la modelización de datos, es adecuado seguir ciertas normas básicas como son la codificación de los valores a un mismo tipo (numérico) y la estandarización o la normalización de todos ellos, intentado promover una misma escala.

# In [6]


from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import StandardScaler



# In [7]

encoder=LabelEncoder()
dfc['country']=dfc[['country']].apply(encoder.fit_transform)
dfc['continent']=dfc[['continent']].apply(encoder.fit_transform)
dfc['buyer']=dfc[['buyer']].apply(encoder.fit_transform)
dfc.head(3)

# Out[7]
...
Label encoding

Label encoding

# In [8]
scaler = StandardScaler()
scaler.fit(dfc)
dfc_scaled = pd.DataFrame(scaler.transform(dfc), columns=dfc.columns )
dfc_scaled.head(3)

# Out[8] ...
Estandarización de datos

Estandarización de los datos

Reducción de la dimensionalidad

Lo siguiente, aunque en este caso quizás no sea de gran utilidad, es llevar a cabo la reducción de la dimensionalidad del conjunto de datos ya perfeccionado. Este tipo de técnicas son usualmente empleadas cuando hay un alto número de métricas y dimensiones en el dataset y muchas de ellas pueden explicarse por medio de otras ya existentes. En este contexto, he optado por aplicar el PCA (Principal Component Analysis), el cual es capaz de describir unas nuevas variables no correlacionadas explicando únicamente a través de ellas el comportamiento del conjunto de datos inicial.

# In [9]


from sklearn.decomposition import PCA

pca = PCA(n_components = dfc_scaled.shape[1], random_state = 1)
dfc_pca = pd.DataFrame(pca.fit_transform(dfc_scaled))

exp_v = pca.explained_variance_ratio_
sum = 0
for ix, i in enumerate(exp_v):
sum = sum + i
if(sum>0.77):
variance=ix + 1
break

plt.figure(figsize = (5, 5))
plt.plot(range(1, pca.n_components + 1), pca.explained_variance_ratio_.cumsum(), marker = 'o', linestyle = '--')
plt.title("77% de varianza explicada con "+str(variance)+" componentes")
plt.xlabel("Número de componentes")
plt.ylabel("Varianza explicada acumulada")
plt.show()

# Out[9] ...
Varianza acumulada explicada

Varianza explicada acumulada

Antiguamente entender los resultados requería algo más de tiempo, sin embargo, en la actualidad gracias a herramientas como ChatGPT podemos detallar y extrapolar de manera fidedigna lo que se ha obtenido (ver celda 11).

# In [10]

cols = ['PC1', 'PC2', 'PC3', 'PC4']
pc = pd.DataFrame(np.round(pca.components_.T[:, 0:4], 2), index = dfc_scaled.columns, columns = cols)

pc

# Out[10] ...
Componentes principales para reducir la dimensionalidad

Componentes principales para reducir la dimensionalidad

# In [ChatGPT]

Actúa como un experto en Aprendizaje automático con Python y teniendo la siguiente mátrix de Componentes Principales, detállame de manera resumida cada columna PC: [AÑADIR AQUÍ LA MATRIZ ANTERIOR]

# Out[ChatGPT]

- PC1: Se enfoca en el comportamiento de compra de los usuarios, con una alta carga en 'transactions' y 'buyer', y una relación con el número de artículos comprados ('items').

- PC2: Captura la variabilidad geográfica, con una carga destacada en 'continent' y 'country', lo que sugiere una relación con la ubicación geográfica de los usuarios o las ventas.

- PC3: Relacionada con el valor monetario de las transacciones, con una alta carga en 'revenue' y una relación con el número de artículos comprados ('items').

- PC4: Posiblemente relacionada con características individuales de los usuarios, con una carga principal en 'user_pseudo_id' y relaciones menores con 'revenue' y 'country'.

Elección del número de clústeres

Tras haber reducido el conjunto de datos, y siempre y cuando no se pierda el comportamiento subyacente de los datos primigenios, es el turno para aplicar los algoritmos deseados. En este caso concreto, algoritmos que nos ayudarán a clusterizar a nuestros usuarios teniendo en cuenta cada una de sus características. Una forma rápida de conocer el número de segmentos que obtendremos, aunque no la única y no mucho menos válida para todo tipo de circunstancias a las que nos enfrentemos en el futuro, es a través del método del codo (Elbow method).

# In [11]

from sklearn.cluster import KMeans
from scipy.spatial.distance import cdist

dfc_kmeans = dfc_pca.copy()

meanDistortions = []

for k in range(1, 15):
kmeans = KMeans(n_clusters = k, random_state = 1, n_init = 'auto')
kmeans.fit(dfc_pca)
prediction = kmeans.predict(dfc_kmeans)
distortion = (
np.sum(np.min(cdist(dfc_kmeans, kmeans.cluster_centers_, 'euclidean'), axis = 1))/ dfc_kmeans.shape[0]
)
meanDistortions.append(distortion)

plt.plot(clusters, meanDistortions, marker = 'o')
plt.xlabel('Número de clústeres (k)')
plt.ylabel('Distorsión')
plt.show()

# Out[11] ...
Método del codo para especificar el número de clústeres

Método del codo para especificar el número de clústeres

Elección del algoritmo de clusterización

Ahora que ya sabemos cuántos grupos de usuarios se generarán, solo quedaría añadir cuantos algoritmos de clusterización creamos convenientes (más información) y enfrentarlos entre ellos, por ejemplo, a través de una puntuación conocida como silueta (silhouette). El que obtenga un valor más alto debería ser el algoritmo que genere los clusters más fiables. Aunque como casi todo en la vida, la experiencia de negocio así como la visualización de los datos podrían ser necesarias para decantarse por uno u otro finalmente. En este punto es importante instalar la librería SKLEARN_EXTRA (‘pip install scikit-learn-extra’).

# In [12]

from sklearn.metrics import silhouette_score


from sklearn_extra.cluster import KMedoids
from sklearn.mixture import GaussianMixture
from sklearn.cluster import AgglomerativeClustering

# In [13]

kmeans = KMeans(n_clusters = 4, random_state = 1, n_init = 'auto')
preds = kmeans.fit_predict((dfc_pca))
score = silhouette_score(dfc_pca, preds)
print(score)

# Out[13]

0.4999637811914792

# In [14]

kmedoids = KMedoids(n_clusters = 4, random_state = 1)
preds = kmedoids.fit_predict((dfc_pca))
score = silhouette_score(dfc_pca, preds)
print(score)

# Out[14]

0.32581576567184256

# In [15]

HCmodel = AgglomerativeClustering(n_clusters = 4, metric = "euclidean", linkage = "ward",)
preds = HCmodel.fit_predict(dfc_pca)
score = silhouette_score(dfc_pca, preds)
print(score)

# Out[15]

0.48844576620165786

# In [16]

gmm = GaussianMixture(n_components=4, random_state=1)
preds = gmm.fit_predict((dfc_pca))
score = silhouette_score(dfc_pca, preds)
print(score)

# Out[16]

0.44381502507146325

Perfilado de los segmentos (clusters)

Por último, solo quedará aplicar el algoritmo seleccionado (el que mayor score ha obtenido, en este caso sería el KMEANS) y “visualizar” los segmentos creados. Unido a esto, al igual que hemos hecho en la celda nueve solo quedará generar el profiling de los diferentes segmentos usando para ello ChatGPT o cualquier otra herramienta similar.

# In [17]

dfk = dfc.copy()

dfk['KM_segments'] = kmeans.labels_
km_cluster_profile = dfk.groupby('KM_segments').mean(numeric_only = True)
km_cluster_profile

# Out[17] ...
Matriz de perfiles (segmentos de usuarios)

Matriz de perfiles (segmentos de usuarios)

# In [ChatGPT]

Teniendo la siguiente matriz de perfiles de clusterización de un algoritmo k-means, detállame de manera resumida cuáles serían los perfiles de cada segmento obtenido: [AÑADIR AQUÍ LA MATRIZ ANTERIOR]

# Out[ChatGPT]

- Segmento 0: Usuarios principalmente del continente americano, con transacciones frecuentes de bajo valor y una proporción alta de compradores. Realizan un número significativo de transacciones y compran una cantidad considerable de artículos.
- Segmento 1: Usuarios del continente americano, con transacciones menos frecuentes pero de mayor valor. Hay una alta proporción de compradores. Realizan menos transacciones pero con un valor monetario más alto y compran una cantidad considerable de artículos.
- Segmento 2: Usuarios del continente asiático, con una variedad de ingresos y una alta proporción de compradores. Realizan un número significativo de transacciones y compras de artículos.
- Segmento 3: Usuarios del continente asiático, con transacciones menos frecuentes pero de mayor valor. Hay una alta proporción de compradores. Realizan menos transacciones pero con un valor monetario más alto y compran una cantidad considerable de artículos.

A partir de este momento, las aplicaciones que se pueden extraer de este desarrollo son infinitas. De hecho, todo esto mostrado no solo nos servirá para analizar a nuestros usuarios, sino que se podrá usar incluso para crear audiencias con los identificadores del usuario como el [user_pseudo_id]. ¡Haz volar tu imaginación!

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *