Cómo realizar la selección de sitios con Places Insights y BigQuery

Introducción

En este documento, se describe cómo crear una solución de selección de sitios combinando el conjunto de datos de Places Insights, los datos geoespaciales públicos en BigQuery, y la API de Place Details.

Mapa de Las Vegas que muestra posibles ubicaciones nuevas para cafeterías con una superposición de datos morada y pines verdes para la competencia existente.

El mapa anterior ilustra el resultado de una demostración que se realizó en Google Cloud Next 2025, que está disponible para ver en YouTube. Puedes ejecutar el código que se usó para generar estos resultados con el notebook de ejemplo.

El desafío empresarial

Imagina que eres propietario de una cadena exitosa de cafeterías y quieres expandirte a un estado nuevo, como Nevada, en el que no tienes presencia. Abrir una ubicación nueva es una inversión importante, y tomar una decisión basada en datos es fundamental para el éxito. ¿Cómo se puede comenzar?

En esta guía, se explica un análisis de varias capas para identificar la ubicación óptima para una nueva cafetería. Comenzaremos con una vista de todo el estado, reduciremos progresivamente nuestra búsqueda a un condado y una zona comercial específicos y, por último, realizaremos un análisis hiperlocal para calificar áreas individuales y detectar brechas en el mercado mediante la representación de los competidores.

Flujo de trabajo de la solución

Este proceso sigue un embudo lógico, que comienza de forma amplia y se vuelve cada vez más detallado para refinar el área de búsqueda y aumentar la confianza en la selección final del sitio.

Requisitos previos y configuración del entorno

Antes de comenzar con el análisis, necesitas un entorno con algunas capacidades clave. Si bien esta guía explica una implementación con SQL y Python, los principios generales se pueden aplicar a otras pilas de tecnología.

Como requisito previo, asegúrate de que tu entorno pueda hacer lo siguiente:

También debes poder visualizar datos geoespaciales en un mapa, lo que es fundamental para interpretar los resultados de cada paso analítico. Hay muchas maneras de lograrlo. Puedes usar herramientas de estadísticas como Looker Studio, que se conectan directamente a BigQuery, o lenguajes de ciencia de datos como Python.

Análisis a nivel estatal: Encuentra el mejor condado

Nuestro primer paso es un análisis amplio para identificar el condado más prometedor de Nevada. Definiremos prometedor como una combinación de una población alta y una alta densidad de restaurantes existentes, lo que indica una cultura sólida de alimentos y bebidas.

Nuestra consulta de BigQuery logra esto aprovechando los componentes de dirección integrados disponibles en el conjunto de datos de Places Insights. La consulta cuenta los restaurantes primero filtrando los datos para incluir solo los lugares dentro del estado de Nevada, usando el campo administrative_area_level_1_name. Luego, refina aún más este conjunto para incluir solo los lugares en los que el array de tipos contiene 'restaurant'. Por último, agrupa estos resultados por nombre del condado (administrative_area_level_2_name) para producir un recuento de cada condado. Este enfoque utiliza la estructura de dirección integrada y preindexada del conjunto de datos.

En este fragmento, se muestra cómo unimos las geometrías del condado con Places Insights y filtramos por un tipo de lugar específico, restaurant:

SELECT WITH AGGREGATION_THRESHOLD
  administrative_area_level_2_name,
  COUNT(*) AS restaurant_count
FROM
  `places_insights___us.places`
WHERE
  -- Filter for the state of Nevada
  administrative_area_level_1_name = 'Nevada'
  -- Filter for places that are restaurants
  AND 'restaurant' IN UNNEST(types)
  -- Filter for operational places only
  AND business_status = 'OPERATIONAL'
  -- Exclude rows where the county name is null
  AND administrative_area_level_2_name IS NOT NULL
GROUP BY
  administrative_area_level_2_name
ORDER BY
  restaurant_count DESC

Un recuento sin procesar de restaurantes no es suficiente; debemos equilibrarlo con los datos de población para obtener una idea real de la saturación y la oportunidad del mercado. Usaremos datos de población de US Census Bureau County Population Totals.

Para comparar estas dos métricas muy diferentes (un recuento de lugares en comparación con un gran número de población), usamos la normalización min-max. Esta técnica escala ambas métricas a un rango común (de 0 a 1). Luego, las combinamos en un solo normalized_score, lo que le da a cada métrica un peso del 50% para una comparación equilibrada.

En este fragmento, se muestra la lógica principal para calcular la puntuación. Combina la población normalizada y los recuentos de restaurantes:

(
    -- Normalize restaurant count (scales to a 0-1 value) and apply 50% weight
    SAFE_DIVIDE(restaurant_count - min_restaurants, max_restaurants - min_restaurants) * 0.5
    +
    -- Normalize population (scales to a 0-1 value) and apply 50% weight
    SAFE_DIVIDE(population_2023 - min_pop, max_pop - min_pop) * 0.5
  ) AS normalized_score

Después de ejecutar la consulta completa, se muestra una lista de los condados, el recuento de restaurantes, la población y la puntuación normalizada. El ordenamiento por normalized_score DESC revela que el condado de Clark es el claro ganador para una mayor investigación como el principal contendiente.

Una tabla de resultados de la búsqueda que enumera los 4 condados principales de Nevada, con el condado de Clark en el primer lugar con una puntuación normalizada de 1.0.

En esta captura de pantalla, se muestran los 4 condados principales por puntuación normalizada. El recuento de población sin procesar se omitió intencionalmente de este ejemplo.

Análisis a nivel del condado: Encuentra las zonas comerciales más concurridas

Ahora que identificamos el condado de Clark, el siguiente paso es acercarnos para encontrar los códigos postales con la mayor actividad comercial. Según los datos de nuestras cafeterías existentes, sabemos que el rendimiento es mejor cuando se ubican cerca de una alta densidad de marcas importantes, por lo que usaremos esto como un proxy para el tráfico peatonal alto.

Esta consulta usa la tabla brands dentro de Places Insights, que contiene información sobre marcas específicas. Se puede consultar esta tabla para descubrir la lista de marcas compatibles. Primero, definimos una lista de nuestras marcas objetivo y, luego, la unimos con el conjunto de datos principal de Places Insights para contar cuántas de estas tiendas específicas se encuentran dentro de cada código postal del condado de Clark.

La forma más eficiente de lograr esto es con un enfoque de dos pasos:

  1. Primero, realizaremos una agregación rápida y no geoespacial para contar las marcas dentro de cada código postal.
  2. En segundo lugar, uniremos esos resultados a un conjunto de datos públicos para obtener los límites del mapa para la visualización.

Cuenta marcas con el campo postal_code_names

Esta primera consulta realiza la lógica de conteo principal. Filtra los lugares en el condado de Clark y, luego, anula el array postal_code_names para agrupar los recuentos de marcas por código postal.

WITH brand_names AS (
  -- First, select the chains we are interested in by name
  SELECT
    id,
    name
  FROM
    `places_insights___us.brands`
  WHERE
    name IN ('7-Eleven', 'CVS', 'Walgreens', 'Subway Restaurants', "McDonald's")
)
SELECT WITH AGGREGATION_THRESHOLD
  postal_code,
  COUNT(*) AS total_brand_count
FROM
  `places_insights___us.places` AS places_table,
  -- Unnest the built-in postal code and brand ID arrays
  UNNEST(places_table.postal_code_names) AS postal_code,
  UNNEST(places_table.brand_ids) AS brand_id
JOIN
  brand_names
  ON brand_names.id = brand_id
WHERE
  -- Filter directly on the administrative area fields in the places table
  places_table.administrative_area_level_2_name = 'Clark County'
  AND places_table.administrative_area_level_1_name = 'Nevada'
GROUP BY
  postal_code
ORDER BY
  total_brand_count DESC

El resultado es una tabla de códigos postales y sus recuentos de marcas correspondientes.

Una tabla de resultados de la búsqueda que enumera los códigos postales y sus recuentos totales de marcas, en la que 89119 tiene el recuento más alto de 38.

Adjunta geometrías de código postal para la asignación

Ahora que tenemos los recuentos, podemos obtener las formas de polígono necesarias para la visualización. Esta segunda consulta toma nuestra primera consulta, la incluye en una expresión de tabla común (CTE) llamada brand_counts_by_zip y une sus resultados a la tabla geo_us_boundaries.zip_codes table pública. Esto adjunta de manera eficiente la geometría a nuestros recuentos precalculados.

WITH brand_counts_by_zip AS (
  -- This will be the entire query from the previous step, without the final ORDER BY (excluded for brevity).
  . . .
)
-- Now, join the aggregated results to the boundaries table
SELECT
  counts.postal_code,
  counts.total_brand_count,
  -- Simplify the geometry for faster rendering in maps
  ST_SIMPLIFY(zip_boundaries.zip_code_geom, 100) AS geography
FROM
  brand_counts_by_zip AS counts
JOIN
  `bigquery-public-data.geo_us_boundaries.zip_codes` AS zip_boundaries
  ON counts.postal_code = zip_boundaries.zip_code
ORDER BY
  counts.total_brand_count DESC

El resultado es una tabla de códigos postales, sus recuentos de marcas correspondientes y la geometría del código postal.

Tabla de resultados de la consulta con códigos postales, recuentos de marcas y datos de polígonos geográficos correspondientes para la visualización.

Podemos visualizar estos datos como un mapa de calor. Las áreas rojas más oscuras indican una mayor concentración de nuestras marcas objetivo, lo que nos indica las zonas más densas comercialmente dentro de Las Vegas.

Un mapa de calor de Las Vegas que indica la mayor concentración de marcas objetivo en rojo y amarillo.

Análisis hiperlocal: Califica áreas de cuadrícula individuales

Después de identificar el área general de Las Vegas, es hora de realizar un análisis detallado. Aquí es donde incorporamos nuestro conocimiento comercial específico. Sabemos que una gran cafetería prospera cerca de otras empresas que están ocupadas durante nuestras horas pico, como la última hora de la mañana y el almuerzo.

Nuestra siguiente consulta es muy específica. Comienza creando una cuadrícula hexagonal detallada sobre el área metropolitana de Las Vegas con el índice geoespacial H3 estándar (en la resolución 8) para analizar el área a un nivel micro. La consulta primero identifica todas las empresas complementarias que están abiertas durante nuestro período pico (lunes, de 10 a.m. a 2 p.m.).

Luego, aplicamos una puntuación ponderada a cada tipo de lugar. Un restaurante cercano es más valioso para nosotros que una tienda de conveniencia, por lo que obtiene un multiplicador más alto. Esto nos da un suitability_score personalizado para cada área pequeña.

En este fragmento, se destaca la lógica de puntuación ponderada, que hace referencia a una marca precalculada (is_open_monday_window) para la verificación del horario de atención:

. . .
(
  COUNTIF('restaurant' IN UNNEST(types) AND is_open_monday_window) * 8 +
  COUNTIF('convenience_store' IN UNNEST(types) AND is_open_monday_window) * 3 +
  COUNTIF('bar' IN UNNEST(types) AND is_open_monday_window) * 7 +
  COUNTIF('tourist_attraction' IN UNNEST(types) AND is_open_monday_window) * 6 +
  COUNTIF('casino' IN UNNEST(types) AND is_open_monday_window) * 7
) AS suitability_score
. . .

Expandir para ver la consulta completa

    -- This query calculates a custom 'suitability score' for different areas in the Las Vegas
-- metropolitan area to identify prime commercial zones. It uses a weighted model based
-- on the density of specific business types that are open during a target time window.

-- Step 1: Pre-filter the dataset to only include relevant places.
-- This CTE finds all places in our target localities (Las Vegas, Spring Valley, etc.) and
-- adds a boolean flag 'is_open_monday_window' for those open during the target time.
WITH PlacesInTargetAreaWithOpenFlag AS (
  SELECT
    point,
    types,
    EXISTS(
      SELECT 1
      FROM UNNEST(regular_opening_hours.monday) AS monday_hours
      WHERE
        monday_hours.start_time <= TIME '10:00:00'
        AND monday_hours.end_time >= TIME '14:00:00'
    ) AS is_open_monday_window
  FROM
    `places_insights___us.places`
  WHERE
    EXISTS (
        SELECT 1 FROM UNNEST(locality_names) AS locality
        WHERE locality IN ('Las Vegas', 'Spring Valley', 'Paradise', 'North Las Vegas', 'Winchester')
    )
    AND administrative_area_level_1_name = 'Nevada'
),
-- Step 2: Aggregate the filtered places into H3 cells and calculate the suitability score.
-- Each place's location is converted to an H3 index (at resolution 8). The query then
-- calculates a weighted 'suitability_score' and individual counts for each business type
-- within that cell.
TileScores AS (
  SELECT WITH AGGREGATION_THRESHOLD
    -- Convert each place's geographic point into an H3 cell index.
    `carto-os.carto.H3_FROMGEOGPOINT`(point, 8) AS h3_index,

    -- Calculate the weighted score based on the count of places of each type
    -- that are open during the target window.
    (
      COUNTIF('restaurant' IN UNNEST(types) AND is_open_monday_window) * 8 +
      COUNTIF('convenience_store' IN UNNEST(types) AND is_open_monday_window) * 3 +
      COUNTIF('bar' IN UNNEST(types) AND is_open_monday_window) * 7 +
      COUNTIF('tourist_attraction' IN UNNEST(types) AND is_open_monday_window) * 6 +
      COUNTIF('casino' IN UNNEST(types) AND is_open_monday_window) * 7
    ) AS suitability_score,

    -- Also return the individual counts for each category for detailed analysis.
    COUNTIF('restaurant' IN UNNEST(types) AND is_open_monday_window) AS restaurant_count,
    COUNTIF('convenience_store' IN UNNEST(types) AND is_open_monday_window) AS convenience_store_count,
    COUNTIF('bar' IN UNNEST(types) AND is_open_monday_window) AS bar_count,
    COUNTIF('tourist_attraction' IN UNNEST(types) AND is_open_monday_window) AS tourist_attraction_count,
    COUNTIF('casino' IN UNNEST(types) AND is_open_monday_window) AS casino_count
  FROM
    -- CHANGED: This now references the CTE with the expanded area.
    PlacesInTargetAreaWithOpenFlag
  -- Group by the H3 index to ensure all calculations are per-cell.
  GROUP BY
    h3_index
),
-- Step 3: Find the maximum suitability score across all cells.
-- This value is used in the next step to normalize the scores to a consistent scale (e.g., 0-10).
MaxScore AS (
  SELECT MAX(suitability_score) AS max_score FROM TileScores
)
-- Step 4: Assemble the final results.
-- This joins the scored tiles with the max score, calculates the normalized score,
-- generates the H3 cell's polygon geometry for mapping, and orders the results.
SELECT
  ts.h3_index,
  -- Generate the hexagonal polygon for the H3 cell for visualization.
  `carto-os.carto.H3_BOUNDARY`(ts.h3_index) AS h3_geography,
  ts.restaurant_count,
  ts.convenience_store_count,
  ts.bar_count,
  ts.tourist_attraction_count,
  ts.casino_count,
  ts.suitability_score,
  -- Normalize the score to a 0-10 scale for easier interpretation.
  ROUND(
    CASE
      WHEN ms.max_score = 0 THEN 0
      ELSE (ts.suitability_score / ms.max_score) * 10
    END,
    2
  ) AS normalized_suitability_score
FROM
  -- A cross join is efficient here as MaxScore contains only one row.
  TileScores ts, MaxScore ms
-- Display the highest-scoring locations first.
ORDER BY
  normalized_suitability_score DESC;
    

Visualizar estas puntuaciones en un mapa revela ubicaciones ganadoras claras. Los mosaicos morados más oscuros, principalmente cerca de Las Vegas Strip y el centro de la ciudad, son las áreas con el mayor potencial para nuestra nueva cafetería.

Un mapa coropletas de Las Vegas que usa una cuadrícula hexagonal púrpura para mostrar las puntuaciones de idoneidad, con tonos más oscuros que indican un mayor potencial.

Análisis de la competencia: Identifica las cafeterías existentes

Nuestro modelo de idoneidad identificó correctamente las zonas más prometedoras, pero una puntuación alta por sí sola no garantiza el éxito. Ahora debemos superponer esto con los datos de la competencia. La ubicación ideal es un área de alto potencial con una baja densidad de cafeterías existentes, ya que buscamos una brecha clara en el mercado.

Para lograr esto, usamos la PLACES_COUNT_PER_H3 función. Esta función está diseñada para mostrar de manera eficiente los recuentos de lugares dentro de una geografía especificada, por celda H3.

Primero, definimos de forma dinámica la geografía para toda el área metropolitana de Las Vegas. En lugar de depender de una sola localidad, consultamos el conjunto de datos público de Overture Maps para obtener los límites de Las Vegas y sus localidades clave circundantes, y los unimos en un solo polígono con ST_UNION_AGG. Luego, pasamos esta área a la función y le pedimos que cuente todas las cafeterías operativas.

Esta consulta define el área metropolitana y llama a la función para obtener los recuentos de cafeterías en celdas H3:

-- Define a variable to hold the combined geography for the Las Vegas metro area.
DECLARE las_vegas_metro_area GEOGRAPHY;

-- Set the variable by fetching the shapes for the five localities from Overture Maps
-- and merging them into a single polygon using ST_UNION_AGG.
SET las_vegas_metro_area = (
  SELECT
    ST_UNION_AGG(geometry)
  FROM
    `bigquery-public-data.overture_maps.division_area`
  WHERE
    country = 'US'
    AND region = 'US-NV'
    AND names.primary IN ('Las Vegas', 'Spring Valley', 'Paradise', 'North Las Vegas', 'Winchester')
);

-- Call the PLACES_COUNT_PER_H3 function with our defined area and parameters.
SELECT
  *
FROM
  `places_insights___us.PLACES_COUNT_PER_H3`(
    JSON_OBJECT(
      -- Use the metro area geography we just created.
      'geography', las_vegas_metro_area,
      -- Specify 'coffee_shop' as the place type to count.
      'types', ["coffee_shop"],
      -- Best practice: Only count places that are currently operational.
      'business_status', ['OPERATIONAL'],
      -- Set the H3 grid resolution to 8.
      'h3_resolution', 8
    )
  );

La función muestra una tabla que incluye el índice de celdas H3, su geometría, el recuento total de cafeterías y una muestra de sus IDs de lugar:

Tabla de resultados de la consulta que muestra celdas H3 con sus recuentos de cafeterías y los IDs de muestra de lugares correspondientes.

Si bien el recuento agregado es útil, es fundamental ver a los competidores reales. Aquí es donde hacemos la transición del conjunto de datos de Places Insights a la Places API. Si extraemos los sample_place_ids de las celdas con la puntuación de idoneidad normalizada más alta, podemos llamar a la API de Place Details para recuperar detalles enriquecidos de cada competidor, como su nombre, dirección, calificación y ubicación.

Esto requiere comparar los resultados de la consulta anterior, en la que se generó la puntuación de idoneidad, y la consulta PLACES_COUNT_PER_H3. Se puede usar el índice de celdas H3 para obtener los recuentos y los IDs de las cafeterías de las celdas con la puntuación de idoneidad normalizada más alta.

En este código de Python, se muestra cómo se podría realizar esta comparación.

    # Isolate the Top 5 Most Suitable H3 Cells
    top_suitability_cells = gdf_suitability.head(5)

    # Extract the 'h3_index' values from these top 5 cells into a list.
    top_h3_indexes = top_suitability_cells['h3_index'].tolist()
    print(f"The top 5 H3 indexes are: {top_h3_indexes}")

    # Now, we find the rows in our DataFrame where the
    # 'h3_cell_index' matches one of the indexes from our top 5 list.

    coffee_counts_in_top_zones = gdf_coffee_shops[
        gdf_coffee_shops['h3_cell_index'].isin(top_h3_indexes)
    ]

Ahora tenemos la lista de IDs de lugar de las cafeterías que ya existen dentro de las celdas H3 con la puntuación de idoneidad más alta, y se pueden solicitar más detalles sobre cada lugar.

Para ello, puedes enviar una solicitud a la API de Place Details directamente para cada ID de lugar o usar una biblioteca cliente para realizar la llamada. Recuerda configurar el FieldMask parámetro para solicitar solo los datos que necesitas.

Por último, combinamos todo en una sola visualización potente. Trazamos nuestro mapa coroplético de idoneidad morado como la capa base y, luego, agregamos pines para cada cafetería individual recuperada de la API de Places. Este mapa final proporciona una vista rápida que sintetiza todo nuestro análisis: las áreas moradas oscuras muestran el potencial y los pines verdes muestran la realidad del mercado actual.

Un mapa de Las Vegas con una cuadrícula hexagonal púrpura que muestra las áreas con alto potencial y pines verdes que indican las cafeterías existentes.

Si buscamos celdas moradas oscuras con pocos o ningún pin, podemos identificar con confianza las áreas exactas que representan la mejor oportunidad para nuestra nueva ubicación.

Un mapa en primer plano de dos áreas moradas de alto potencial en Las Vegas, en el que se muestran las ubicaciones de la competencia y las brechas claras en el mercado.

Las dos celdas anteriores tienen una puntuación de idoneidad alta, pero algunas brechas claras que podrían ser ubicaciones potenciales para nuestra nueva cafetería.

Conclusión

En este documento, pasamos de una pregunta a nivel estatal de ¿dónde expandirse? a una respuesta local respaldada por datos. Si superpones diferentes conjuntos de datos y aplicas una lógica comercial personalizada, puedes reducir de forma sistemática el riesgo asociado con una decisión comercial importante. Este flujo de trabajo, que combina la escala de BigQuery, la riqueza de Places Insights y el detalle en tiempo real de la API de Places, proporciona una plantilla potente para cualquier organización que desee usar la inteligencia de ubicación para el crecimiento estratégico.

Próximos pasos

  • Adapta este flujo de trabajo con tu propia lógica comercial, geografías objetivo y conjuntos de datos patentados.
  • Explora otros campos de datos en el conjunto de datos de Places Insights, como los recuentos de opiniones, los niveles de precios y las calificaciones de los usuarios, para enriquecer aún más tu modelo.
  • Automatiza este proceso para crear un panel interno de selección de sitios que se pueda usar para evaluar mercados nuevos de forma dinámica.

Obtén más información en la documentación:

Colaboradores

Henrik Valve | DevX Ingeniero