El objetivo de este post es ayudar a los usuarios de BigQuery a utilizar BigQuery Machine Learning (BigQuery ML) con el fin de realizar una segmentación en sus datos utilizando el algoritmo K-means.
Dentro de Google Cloud Platform destacamos BigQuery por su uso en el tratamiento de grandes volúmenes de datos. BigQuery nos permite realizar análisis interactivos de nuestros datasets a través de sencillas consultas SQL pero, además, por la integración de BigQuery Machine Learning, la herramienta es capaz de crear y ejecutar modelos de aprendizaje automático de una manera tan simple como con una query de SQL estándar desde la misma consola.
Si bien es cierto que la capacidad de realizar modelos en BigQuery ML es todavía limitada, se pueden realizar con rapidez regresiones lineales, regresiones logísticas (binomial o multinomial) y análisis clustering o de conglomerados. Este último es el que nos interesa por su posible uso para cualquier tipo de segmentación, como podría ser el caso de una segmentación de audiencias.
El algoritmo implementado en BigQuery ML es el conocido como K-means o de las K-medias (MacQueen,1967), cuyo objetivo sería encontrar segmentos homogéneos en población que se compongan de individuos con comportamientos diferentes con respecto a otros grupos teniendo en cuenta una distancia. Para esto, el número K de grupos debe estar pre-definido. El algoritmo funcionaría de la siguiente manera:
- Se elige un número aleatorio K de clústeres.
- Se seleccionan aleatoriamente k observaciones como centroides de los K clústeres.
- Se asigna cada observación a su centroide más próximo en función de la distancia empleada.
- Para cada clúster se recalcula su centroide.
- Se repiten los pasos 3 y 4 hasta que las asignaciones no cambien o se logre el máximo de iteraciones acordado.
Para realizar este ejemplo utilizaré mi propio proyecto de pruebas y los datos los extraeré del dataset público bigquery-public-data, concretamente de la tabla bigquery-public-data:ml_datasets.census_adult_income, que contiene datos sobre características socioeconómicas de 32.561 adultos. Se ha de tener en cuenta que el dataset donde se va a realizar el modelo debe estar creado en la misma ubicación que los datos de origen; en mi caso, por tanto, será USA.
En el siguiente paso es la creación del modelo. Se realizará una consulta donde se cree un modelo en el dataset que se acaba de elaborar, de modo que se utilice la función CREATE OR REPLACE MODEL ‘*dataset*.*modelo_creado*’. Así se localizará el modelo dentro del dataset como si de una tabla de BigQuery se tratase. Se han de elegir como opciones (OPTIONS) ‘kmeans’ ‘num_clusters’=X, ‘standardize_features’ = TRUE y ‘distance_type’=’euclidean’, para elegir el número de centroides, estadanarizar los valores y elegir el tipo de distancia como euclídea. Existen otras opciones como KMEANS_INIT_METHOD o KMEANS_INIT_COL que establecerían un punto de referencia al algoritmo. Después se seleccionarán las variables que se necesitarán para el modelo. Si existiera alguna variable sobre la que se quisieran hacer los grupos, como un user_id, sería exceptuada mediante el comando EXCEPT(), pero no sucede eso en este caso.
- CREATE OR REPLACE MODEL
- bqml_pruebas.adult_income_4
- OPTIONS
- (model_type='kmeans',
- num_clusters=4,
- standardize_features = TRUE,
- distance_type= 'euclidean') AS
- SELECT *
- FROM `bigquery-public-data.ml_datasets.census_adult_income`
Para poder elegir el número óptimo de clusters se precisa realizar varios modelos con diferentes números de cluster y después comparar ambos con el método del codo o elbow method. Una vez hayamos ejecutado los distintos, podemos compararlos con la siguiente consulta:
- SELECT *
- FROM
- (SELECT
- '2' AS Size,
- *
- FROM
- ML.EVALUATE(MODEL `pruebas-jaime-235311.bqml_pruebas.adult_income_2`)
- UNION ALL
- SELECT
- '3' AS Size,
- *
- FROM
- ML.EVALUATE(MODEL `pruebas-jaime-235311.bqml_pruebas.adult_income_3`)
- UNION ALL
- SELECT
- '4' AS Size,
- *
- FROM
- ML.EVALUATE(MODEL `pruebas-jaime-235311.bqml_pruebas.adult_income_4`)
- UNION ALL
- (…)
- )
- ORDER BY 1 ASC
Cuando la consulta nos muestre resultados, podemos pasar los datos a DataStudio con ‘EXPLORAR DATOS’, para ahí buscar el codo mediante un gráfico de líneas con los resultados de mean_squared_distance. Según los resultados el modelo a seguir será el cluster con 4 centroides como se había planteado inicialmente.
El paso que sigue es la exploración del modelo. Dentro del dataset creado para los modelos, se encontrará el modelo concreto como si de una tabla se tratase y en la parte de evaluación tendremos numerosa información sobre el modelo, a saber: las métricas, las variables numéricas y las variables categóricas.
En el apartado Atributos numéricos se muestran las características del centroide, o representante del grupo, para cada una de las variables numéricas. Estas características representan al grupo en su conjunto. Además, también muestra el tamaño del grupo con la variable Cantidad.

En el apartado Atributos categóricos podemos observar cómo se comporta cada grupo con respecto a las diferentes variables categóricas incluidas en el modelo. Así, podemos ver cómo los clusters 3 y 4 se componen mayoritariamente por hombres y que los grupos 1 y 2 están compuestos en su mayoría por never-married.

A veces en la parte de Atributos numéricos no aparecen todos los existentes en el modelo, por lo que otra opción para extraer los datos de los centroides de las variables numéricas sería mediante una consulta al modelo para después exportar los datos a DataStudio y poder trabajar allí con ellos. La función ML.CENTROIDS() extraerá los centroides de las variables.
- SELECT
- *
- FROM
- ML.CENTROIDS(MODEL bqml_pruebas.adult_income_4)
- ORDER BY
- centroid_id
Sin embargo, esta función nos muestra los datos de forma anidada, por lo que se procederá a realizar un desanidado de los datos creando una variable (T) formada por los centroides y los clusters de la que se extraerán los resultados para cada variable.
- WITH
- T AS (
- SELECT
- centroid_id,
- ARRAY_AGG(STRUCT(feature AS name,
- ROUND(numerical_value,1) AS value)
- ORDER BY
- centroid_id) AS cluster
- FROM
- ML.CENTROIDS(MODEL bqml_pruebas.adult_income_4)
- GROUP BY
- centroid_id )
- SELECT
- CONCAT('Cluster#', CAST(centroid_id AS STRING)) AS centroid,
- (
- SELECT
- value
- FROM
- UNNEST(cluster)
- WHERE
- name = 'age') AS age,
- (
- SELECT
- value
- FROM
- UNNEST(cluster)
- WHERE
- name = 'education_num') AS education_num,
- (
- SELECT
- value
- FROM
- UNNEST(cluster)
- WHERE
- name = 'capital_gain') AS capital_gain,
- (
- SELECT
- value
- FROM
- UNNEST(cluster)
- WHERE
- name = 'capital_loss') AS capital_loss,
- (
- SELECT
- value
- FROM
- UNNEST(cluster)
- WHERE
- name = 'hours_per_week') AS hours_per_week,
- FROM
- T
- ORDER BY 1 ASC
Si exploramos los resultados mediante Google Data Studio podemos utilizarlos para realizar diferentes comparaciones entre los centroides, como se muestra a continuación usando un mapa de calor:

Finalmente, se realiza la asignación de los clusters a los datos originales. Para esto se seleccionan los datos de la tabla original y los clusters y se unirán. En el caso de haber sido usuarios identificados mediante un ID, la clave de unión será ese ID.
- SELECT
- Data.*,
- Clusters.CENTROID_ID
- FROM
- `bigquery-public-data.ml_datasets.census_adult_income` AS Data,
- ML.PREDICT(MODEL bqml_pruebas.adult_income_4, TABLE `bigquery-public-data.ml_datasets.census_adult_income`) AS Clusters
Una vez hecho esto, podemos comprobar cómo se comportan los distintos grupos si tenemos algunas variables no incluidas en el modelo. Si en este análisis se hubiera utilizado como variable exceptuada algún ID (como user_id), mediante distintos matcheos podríamos comprobar cómo se comportan los usuarios y los grupos incluso en periodos siguientes al estudio. K-means es útil para lograr una buena segmentación de los usuarios, y en última instancia nos ayudaría con la siguiente labor de activación.