Sélectionner un site à l'aide de Places Insights et BigQuery

Introduction

Ce document explique comment créer une solution de sélection de sites en combinant l'ensemble de données Places Insights, les données géospatiales publiques dans BigQuery et l'API Place Details.

Il est basé sur une démonstration présentée lors de Google Cloud Next 2025, disponible sur YouTube.

Carte de Las Vegas montrant de potentiels nouveaux emplacements de cafés avec une superposition de données violettes et des repères verts pour les concurrents existants.

Les défis de l'entreprise

Imaginez que vous possédez une chaîne de cafés à succès et que vous souhaitez vous étendre à un nouvel État, comme le Nevada, où vous n'êtes pas encore présent. L'ouverture d'un nouveau magasin représente un investissement important. Il est donc essentiel de prendre une décision basée sur les données pour assurer le succès de l'opération. Et par où commencer ?

Ce guide vous explique comment effectuer une analyse multicouche pour identifier l'emplacement idéal d'un nouveau café. Nous commencerons par une vue à l'échelle de l'État, puis nous affinerons progressivement notre recherche pour cibler un comté et une zone commerciale spécifiques. Enfin, nous effectuerons une analyse hyperlocale pour évaluer des zones individuelles et identifier les lacunes du marché en cartographiant les concurrents.

Solution workflow

Ce processus suit un entonnoir logique, en commençant par une zone large et en devenant progressivement plus précis pour affiner la zone de recherche et accroître la confiance dans la sélection finale du site.

Conditions préalables et configuration de l'environnement

Avant de vous lancer dans l'analyse, vous avez besoin d'un environnement doté de quelques fonctionnalités clés. Bien que ce guide présente une implémentation utilisant SQL et Python, les principes généraux peuvent être appliqués à d'autres piles technologiques.

Avant de commencer, assurez-vous que votre environnement peut :

Vous devez également être en mesure de visualiser les données géospatiales sur une carte, ce qui est essentiel pour interpréter les résultats de chaque étape analytique. Pour ce faire, plusieurs méthodes s'offrent à vous. Vous pouvez utiliser des outils de BI tels que Looker Studio, qui se connectent directement à BigQuery, ou des langages de science des données tels que Python.

Analyse au niveau de l'État : trouver le meilleur comté

Notre première étape consiste à effectuer une analyse générale pour identifier le comté le plus prometteur du Nevada. Nous définissons une ville prometteuse comme une ville qui combine une population élevée et une forte densité de restaurants existants, ce qui indique une forte culture de la restauration.

Notre requête BigQuery y parvient en exploitant les composants d'adresse intégrés disponibles dans l'ensemble de données Places Insights. La requête compte les restaurants en filtrant d'abord les données pour n'inclure que les lieux situés dans l'État du Nevada, à l'aide du champ administrative_area_level_1_name. Il affine ensuite cet ensemble pour n'inclure que les lieux où le tableau des types contient "restaurant". Enfin, il regroupe ces résultats par nom de comté (administrative_area_level_2_name) pour produire un décompte pour chaque comté. Cette approche utilise la structure d'adresse préindexée intégrée à l'ensemble de données.

Cet extrait montre comment joindre des géométries de comté à Places Insights et filtrer un type de lieu spécifique, 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 nombre brut de restaurants ne suffit pas. Nous devons l'équilibrer avec les données démographiques pour avoir une idée précise de la saturation et des opportunités du marché. Nous utiliserons les données démographiques de la page US Census Bureau County Population Totals.

Pour comparer ces deux métriques très différentes (un nombre de lieux par rapport à un grand nombre de personnes), nous utilisons la normalisation min-max. Cette technique met à l'échelle les deux métriques dans une plage commune (de 0 à 1). Nous les combinons ensuite dans un seul normalized_score, en attribuant à chaque métrique une pondération de 50 % pour une comparaison équilibrée.

Cet extrait montre la logique de base pour calculer le score. Elle combine le nombre normalisé d'habitants et de restaurants :

(
    -- 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

Une fois la requête complète exécutée, une liste des comtés, du nombre de restaurants, de la population et du score normalisé est renvoyée. Si vous triez les résultats par normalized_score DESC, le comté de Clark apparaît clairement comme le grand gagnant et mérite donc d'être étudié plus en détail.

Tableau des résultats de la requête listant les quatre principaux comtés du Nevada, le comté de Clark étant classé en première position avec un score normalisé de 1.0.

Cette capture d'écran montre les quatre premiers comtés par score normalisé. Le nombre brut de la population a été volontairement omis de cet exemple.

Analyse au niveau des comtés : identifiez les zones commerciales les plus fréquentées

Maintenant que nous avons identifié le comté de Clark, l'étape suivante consiste à faire un zoom avant pour trouver les codes postaux où l'activité commerciale est la plus élevée. D'après les données de nos cafés existants, nous savons que les performances sont meilleures lorsqu'ils sont situés à proximité d'une forte densité de grandes marques. Nous utiliserons donc cette information comme indicateur de trafic piéton élevé.

Cette requête utilise la table brands dans Places Insights, qui contient des informations sur des marques spécifiques. Vous pouvez interroger ce tableau pour découvrir la liste des marques acceptées. Nous définissons d'abord une liste de nos marques cibles, puis nous la joignons à l'ensemble de données principal Places Insights pour compter le nombre de ces magasins spécifiques qui se trouvent dans chaque code postal du comté de Clark.

La méthode la plus efficace pour y parvenir consiste à procéder en deux étapes :

  1. Nous allons d'abord effectuer une agrégation rapide et non géospatiale pour compter les marques dans chaque code postal.
  2. Ensuite, nous joindrons ces résultats à un ensemble de données public pour obtenir les limites de la carte à visualiser.

Compter les marques à l'aide du champ "postal_code_names"

Cette première requête effectue la logique de comptage de base. Elle filtre les lieux du comté de Clark, puis annule l'imbrication du tableau postal_code_names pour regrouper les nombres de marques par code 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

Le résultat est un tableau des codes postaux et du nombre de marques correspondantes.

Tableau des résultats de la requête listant les codes postaux et le nombre total de marques associées, avec le code postal 89119 qui compte le plus grand nombre de marques (38).

Associer des géométries de code postal pour la cartographie

Maintenant que nous avons les nombres, nous pouvons obtenir les formes polygonales nécessaires à la visualisation. Cette deuxième requête reprend notre première requête, l'encapsule dans une expression de table commune (CTE) nommée brand_counts_by_zip et joint ses résultats à la table publique geo_us_boundaries.zip_codes table. Cela permet d'associer efficacement la géométrie à nos nombres précalculés.

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

Le résultat est un tableau des codes postaux, du nombre de marques correspondantes et de la géométrie des codes postaux.

Table des résultats de la requête avec les codes postaux, le nombre de marques et les données de polygones géographiques correspondantes pour la visualisation.

Nous pouvons visualiser ces données sous forme de carte de densité. Les zones en rouge foncé indiquent une plus forte concentration de nos marques cibles, ce qui nous oriente vers les zones les plus denses commercialement à Las Vegas.

Carte thermique de Las Vegas indiquant la plus forte concentration de marques cibles en rouge et en jaune.

Analyse hyper-locale : attribuer un score à chaque zone de la grille

Maintenant que vous avez identifié la zone générale de Las Vegas, il est temps de procéder à une analyse précise. C'est là que nous ajoutons nos connaissances spécifiques sur l'entreprise. Nous savons qu'un bon café prospère à proximité d'autres établissements très fréquentés pendant nos heures de pointe, comme la fin de matinée et le déjeuner.

Notre prochaine requête est très spécifique. Il commence par créer une grille hexagonale précise sur l'agglomération de Las Vegas à l'aide de l'index géospatial H3 standard (à la résolution 8) pour analyser la zone au niveau micro. La requête identifie d'abord tous les établissements complémentaires ouverts pendant notre période de pointe (lundi, de 10h à 14h).

Nous appliquons ensuite un score pondéré à chaque type de lieu. Un restaurant à proximité est plus intéressant pour nous qu'une supérette. Il bénéficie donc d'un multiplicateur plus élevé. Cela nous donne un suitability_score personnalisé pour chaque petite zone.

Cet extrait met en évidence la logique de scoring pondéré, qui fait référence à un indicateur précalculé (is_open_monday_window) pour la vérification des horaires d'ouverture :

. . .
(
  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
. . .

Développer pour afficher la requête complète

    -- 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;
    

La visualisation de ces scores sur une carte révèle clairement les lieux gagnants. Les zones les plus foncées, principalement près du Strip et du centre-ville de Las Vegas, sont celles qui présentent le plus grand potentiel pour notre nouveau café.

Carte choroplèthe de Las Vegas utilisant une grille hexagonale violette pour afficher les scores d&#39;adéquation, les nuances plus foncées indiquant un potentiel plus élevé.

Analyse de la concurrence : identifier les cafés existants

Notre modèle d'adéquation a identifié avec succès les zones les plus prometteuses, mais un score élevé ne garantit pas à lui seul le succès. Nous devons maintenant superposer ces données avec celles des concurrents. L'emplacement idéal est une zone à fort potentiel avec une faible densité de cafés existants, car nous recherchons un manque clair sur le marché.

Pour ce faire, nous utilisons la fonction PLACES_COUNT_PER_H3. Cette fonction est conçue pour renvoyer efficacement le nombre de lieux dans une zone géographique spécifiée, par cellule H3.

Tout d'abord, nous définissons dynamiquement la zone géographique pour l'ensemble de l'agglomération de Las Vegas. Au lieu de nous appuyer sur une seule localité, nous interrogeons l'ensemble de données public Overture Maps pour obtenir les limites de Las Vegas et de ses principales localités environnantes, en les fusionnant en un seul polygone avec ST_UNION_AGG. Nous transmettons ensuite cette zone à la fonction, en lui demandant de compter tous les cafés opérationnels.

Cette requête définit la zone métropolitaine et appelle la fonction pour obtenir le nombre de cafés dans les cellules 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 fonction renvoie un tableau qui inclut l'index de cellule H3, sa géométrie, le nombre total de cafés et un échantillon de leurs ID de lieu :

Table des résultats de la requête affichant des cellules H3 avec le nombre de cafés et les exemples d&#39;ID de lieux correspondants.

Bien que le nombre total soit utile, il est essentiel de voir les concurrents réels. C'est ici que nous passons de l'ensemble de données Places Insights à l'API Places. En extrayant les sample_place_ids des cellules ayant le score d'adéquation normalisé le plus élevé, nous pouvons appeler l'API Place Details pour récupérer des informations détaillées sur chaque concurrent, comme son nom, son adresse, sa note et sa localisation.

Pour cela, vous devez comparer les résultats de la requête précédente, pour laquelle le score d'adéquation a été généré, et la requête PLACES_COUNT_PER_H3. L'index de cellule H3 peut être utilisé pour obtenir le nombre et les ID des cafés à partir des cellules ayant le score d'adéquation normalisé le plus élevé.

Ce code Python montre comment effectuer cette comparaison.

    # 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)
    ]

Maintenant que nous avons la liste des ID de lieux pour les cafés qui existent déjà dans les cellules H3 avec le score d'adéquation le plus élevé, nous pouvons demander plus d'informations sur chaque lieu.

Pour ce faire, vous pouvez envoyer une requête à l'API Place Details directement pour chaque ID de lieu ou utiliser une bibliothèque cliente pour effectuer l'appel. N'oubliez pas de définir le paramètre FieldMask pour ne demander que les données dont vous avez besoin.

Enfin, nous combinons le tout dans une visualisation unique et puissante. Nous représentons notre carte choroplèthe de pertinence violette comme calque de base, puis nous ajoutons des repères pour chaque café récupéré à partir de l'API Places. Cette carte finale offre une vue d'ensemble qui synthétise l'intégralité de notre analyse : les zones violet foncé indiquent le potentiel, et les repères verts montrent la réalité du marché actuel.

Carte de Las Vegas avec une grille hexagonale violette indiquant les zones à fort potentiel et des épingles vertes indiquant les cafés existants.

En recherchant les cellules violet foncé avec peu ou pas de repères, nous pouvons identifier avec précision les zones qui représentent la meilleure opportunité pour notre nouvel emplacement.

Gros plan sur une carte de deux zones violettes à fort potentiel à Las Vegas, montrant les emplacements des concurrents et les lacunes claires du marché.

Les deux cellules ci-dessus présentent un score d'adéquation élevé, mais aussi des lacunes évidentes qui pourraient correspondre à des emplacements potentiels pour notre nouveau café.

Conclusion

Dans ce document, nous sommes passés d'une question à l'échelle de l'État (où se développer ?) à une réponse locale basée sur les données. En superposant différents ensembles de données et en appliquant une logique métier personnalisée, vous pouvez réduire systématiquement le risque associé à une décision commerciale majeure. Ce workflow, qui combine l'évolutivité de BigQuery, la richesse des insights sur les lieux et la précision en temps réel de l'API Places, constitue un modèle puissant pour toute organisation souhaitant utiliser l'intelligence géographique pour sa croissance stratégique.

Étapes suivantes

  • Adaptez ce workflow à votre propre logique métier, à vos zones géographiques cibles et à vos ensembles de données propriétaires.
  • Explorez d'autres champs de données dans l'ensemble de données Places Insights, tels que le nombre d'avis, les niveaux de prix et les notes des utilisateurs, pour enrichir davantage votre modèle.
  • Automatisez ce processus pour créer un tableau de bord interne de sélection de sites qui peut être utilisé pour évaluer de nouveaux marchés de manière dynamique.

Pour en savoir plus, consultez la documentation :

Contributeurs

Henrik Valve | Ingénieur DevX