ロンドンのレンタル自転車のデータセットをクラスタ化するための K 平均法モデルを作成する

このチュートリアルでは、BigQuery ML の k 平均法モデルを使用して、データセット内のクラスタを識別する方法について説明します。

データをクラスタにグループ化する k 平均法アルゴリズムは、教師なし機械学習の一種です。教師あり機械学習が予測分析を目的としているのとは異なり、教師なし機械学習では記述分析が目的となります。教師なし機械学習は、データを理解してデータドリブンの意思決定を行うのに役立ちます。

このチュートリアルのクエリでは、地理空間分析で使用できる地理関数を使用します。詳細については、地理空間分析の概要をご覧ください。

このチュートリアルでは、ロンドンのレンタル自転車の一般公開データセットを使用します。また、開始時と停止時のタイムスタンプ、ステーション名、乗車時間も含まれます。

データセットを作成する

k 平均法モデルを格納する BigQuery データセットを作成します。

  1. Google Cloud コンソールで、[BigQuery] ページに移動します。

    [BigQuery] ページに移動

  2. 左側のペインで、 [エクスプローラ] をクリックします。

    エクスプローラ ペインのボタンがハイライト表示されている。

    左側のペインが表示されていない場合は、 左側のペインを開くをクリックしてペインを開きます。

  3. [エクスプローラ] ペインで、プロジェクト名をクリックします。

  4. 「アクションを表示」> [データセットを作成] をクリックします。

    データセットを作成する。

  5. [データセットを作成する] ページで、次の操作を行います。

    • [データセット ID] に「bqml_tutorial」と入力します。

    • [ロケーション タイプ] で、[マルチリージョン] を選択し、[EU(欧州連合の複数のリージョン)] を選択します。

      ロンドンのレンタル自転車の一般公開データセットは、EUマルチリージョンロケーションに格納されています。データセットも同じロケーションに存在する必要があります。

    • 残りのデフォルトの設定は変更せず、[データセットを作成] をクリックします。

      データセットの作成ページ。

トレーニング データを調べる

k 平均法モデルのトレーニングに使用するデータを調べます。このチュートリアルでは、以下の属性に基づいて自転車ステーションのクラスタリングを行います。

  • レンタル期間
  • 1 日あたりの利用数
  • 市中心部からの距離

SQL

このクエリは、start_station_name 列と duration 列を含む自転車レンタルのデータを抽出し、このデータをステーション情報に結合します。これには、市の中心部から駅までの距離を含む計算列を作成することが含まれます。そして、平均乗車時間と利用数を含んだ stationstats 列でステーションの属性を計算します。この計算には、計算された distance_from_city_center 列も含まれます。

トレーニング データを調べる手順は次のとおりです。

  1. Google Cloud コンソールで、[BigQuery] ページに移動します。

    [BigQuery] に移動

  2. クエリエディタに次のクエリを貼り付け、[実行] をクリックします。

    WITH
    hs AS (
      SELECT
        h.start_station_name AS station_name,
        IF(
          EXTRACT(DAYOFWEEK FROM h.start_date) = 1
            OR EXTRACT(DAYOFWEEK FROM h.start_date) = 7,
          'weekend',
          'weekday') AS isweekday,
        h.duration,
        ST_DISTANCE(ST_GEOGPOINT(s.longitude, s.latitude), ST_GEOGPOINT(-0.1, 51.5)) / 1000
          AS distance_from_city_center
      FROM
        `bigquery-public-data.london_bicycles.cycle_hire` AS h
      JOIN
        `bigquery-public-data.london_bicycles.cycle_stations` AS s
        ON
          h.start_station_id = s.id
      WHERE
        h.start_date
        BETWEEN CAST('2015-01-01 00:00:00' AS TIMESTAMP)
        AND CAST('2016-01-01 00:00:00' AS TIMESTAMP)
    ),
    stationstats AS (
      SELECT
        station_name,
        isweekday,
        AVG(duration) AS duration,
        COUNT(duration) AS num_trips,
        MAX(distance_from_city_center) AS distance_from_city_center
      FROM
        hs
      GROUP BY
        station_name, isweekday
    )
    SELECT *
    FROM
    stationstats
    ORDER BY
    distance_from_city_center ASC;

結果は次のようになります。

クエリ結果

BigQuery DataFrames

このサンプルを試す前に、BigQuery DataFrames を使用した BigQuery クイックスタートの手順に沿って BigQuery DataFrames を設定してください。詳細については、BigQuery DataFrames のリファレンス ドキュメントをご覧ください。

BigQuery に対する認証を行うには、アプリケーションのデフォルト認証情報を設定します。詳細については、ローカル開発環境の ADC の設定をご覧ください。

import datetime
import typing

import pandas as pd
from shapely.geometry import Point

import bigframes
import bigframes.bigquery as bbq
import bigframes.geopandas
import bigframes.pandas as bpd

bigframes.options.bigquery.project = your_gcp_project_id
# Compute in the EU multi-region to query the London bicycles dataset.
bigframes.options.bigquery.location = "EU"

# Extract the information you'll need to train the k-means model in this
# tutorial. Use the read_gbq function to represent cycle hires
# data as a DataFrame.
h = bpd.read_gbq(
    "bigquery-public-data.london_bicycles.cycle_hire",
    col_order=["start_station_name", "start_station_id", "start_date", "duration"],
).rename(
    columns={
        "start_station_name": "station_name",
        "start_station_id": "station_id",
    }
)

# Use GeoSeries.from_xy and BigQuery.st_distance to analyze geographical
# data. These functions determine spatial relationships between
# geographical features.
cycle_stations = bpd.read_gbq("bigquery-public-data.london_bicycles.cycle_stations")
s = bpd.DataFrame(
    {
        "id": cycle_stations["id"],
        "xy": bigframes.geopandas.GeoSeries.from_xy(
            cycle_stations["longitude"], cycle_stations["latitude"]
        ),
    }
)
s_distance = bbq.st_distance(s["xy"], Point(-0.1, 51.5), use_spheroid=False) / 1000
s = bpd.DataFrame({"id": s["id"], "distance_from_city_center": s_distance})

# Define Python datetime objects in the UTC timezone for range comparison,
# because BigQuery stores timestamp data in the UTC timezone.
sample_time = datetime.datetime(2015, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc)
sample_time2 = datetime.datetime(2016, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc)

h = h.loc[(h["start_date"] >= sample_time) & (h["start_date"] <= sample_time2)]

# Replace each day-of-the-week number with the corresponding "weekday" or
# "weekend" label by using the Series.map method.
h = h.assign(
    isweekday=h.start_date.dt.dayofweek.map(
        {
            0: "weekday",
            1: "weekday",
            2: "weekday",
            3: "weekday",
            4: "weekday",
            5: "weekend",
            6: "weekend",
        }
    )
)

# Supplement each trip in "h" with the station distance information from
# "s" by merging the two DataFrames by station ID.
merged_df = h.merge(
    right=s,
    how="inner",
    left_on="station_id",
    right_on="id",
)

# Engineer features to cluster the stations. For each station, find the
# average trip duration, number of trips, and distance from city center.
stationstats = typing.cast(
    bpd.DataFrame,
    merged_df.groupby(["station_name", "isweekday"]).agg(
        {"duration": ["mean", "count"], "distance_from_city_center": "max"}
    ),
)
stationstats.columns = pd.Index(
    ["duration", "num_trips", "distance_from_city_center"]
)
stationstats = stationstats.sort_values(
    by="distance_from_city_center", ascending=True
).reset_index()

# Expected output results: >>> stationstats.head(3)
# station_name	isweekday duration  num_trips	distance_from_city_center
# Borough Road...	weekday	    1110	    5749	    0.12624
# Borough Road...	weekend	    2125	    1774	    0.12624
# Webber Street...	weekday	    795	        6517	    0.164021
#   3 rows × 5 columns

k 平均法モデルを作成する

ロンドンのレンタル自転車のトレーニング データを使用して k 平均法モデルを作成します。

SQL

次のクエリの CREATE MODEL ステートメントでは、使用するクラスタの数(4)を指定します。この列に特徴が含まれていないため、SELECT ステートメントで EXCEPT 句を使って station_name 列を除外します。クエリによって station_name ごとに個別に行が作成されますが、SELECT ステートメントにより特徴だけが抽出されます。

k 平均法モデルを作成する手順は次のとおりです。

  1. Google Cloud コンソールで、[BigQuery] ページに移動します。

    [BigQuery] に移動

  2. クエリエディタに次のクエリを貼り付け、[実行] をクリックします。

    CREATE OR REPLACE MODEL `bqml_tutorial.london_station_clusters`
    OPTIONS (
      model_type = 'kmeans',
      num_clusters = 4)
    AS
    WITH
    hs AS (
      SELECT
        h.start_station_name AS station_name,
        IF(
          EXTRACT(DAYOFWEEK FROM h.start_date) = 1
            OR EXTRACT(DAYOFWEEK FROM h.start_date) = 7,
          'weekend',
          'weekday') AS isweekday,
        h.duration,
        ST_DISTANCE(ST_GEOGPOINT(s.longitude, s.latitude), ST_GEOGPOINT(-0.1, 51.5)) / 1000
          AS distance_from_city_center
      FROM
        `bigquery-public-data.london_bicycles.cycle_hire` AS h
      JOIN
        `bigquery-public-data.london_bicycles.cycle_stations` AS s
        ON
          h.start_station_id = s.id
      WHERE
        h.start_date
        BETWEEN CAST('2015-01-01 00:00:00' AS TIMESTAMP)
        AND CAST('2016-01-01 00:00:00' AS TIMESTAMP)
    ),
    stationstats AS (
      SELECT
        station_name,
        isweekday,
        AVG(duration) AS duration,
        COUNT(duration) AS num_trips,
        MAX(distance_from_city_center) AS distance_from_city_center
      FROM
        hs
      GROUP BY
        station_name, isweekday
    )
    SELECT *
    EXCEPT (station_name, isweekday)
    FROM
    stationstats;

BigQuery DataFrames

このサンプルを試す前に、BigQuery DataFrames を使用した BigQuery クイックスタートの手順に沿って BigQuery DataFrames を設定してください。詳細については、BigQuery DataFrames のリファレンス ドキュメントをご覧ください。

BigQuery に対する認証を行うには、アプリケーションのデフォルト認証情報を設定します。詳細については、ローカル開発環境の ADC の設定をご覧ください。


from bigframes.ml.cluster import KMeans

# To determine an optimal number of clusters, construct and fit several
# K-Means objects with different values of num_clusters, find the error
# measure, and pick the point at which the error measure is at its minimum
# value.
cluster_model = KMeans(n_clusters=4)
cluster_model.fit(stationstats)
cluster_model.to_gbq(
    your_model_id,  # For example: "bqml_tutorial.london_station_clusters"
    replace=True,
)

データクラスタを解釈する

モデルの [評価] タブの情報は、モデルによって生成されたクラスタを解釈するのに役立ちます。

モデルの評価情報を表示する手順は次のとおりです。

  1. Google Cloud コンソールで、[BigQuery] ページに移動します。

    BigQuery に移動

  2. 左側のペインで、 [エクスプローラ] をクリックします。

    エクスプローラ ペインのボタンがハイライト表示されている。

  3. [エクスプローラ] ペインで、プロジェクトを開き、[データセット] をクリックします。

  4. bqml_tutorial データセットをクリックし、[モデル] タブに移動します。

  5. london_station_clusters モデルを選択します。

  6. [評価] タブを選択します。このタブには、k 平均法モデルによって識別されたクラスタが可視化されます。[数値特徴] セクションの棒グラフには、各セントロイドの最も重要な数値特徴値が表示されます。各セントロイドは、特定のデータクラスタを表します。可視化する特徴をプルダウン メニューから選択できます。

    数値特徴のグラフ

    このモデルでは、次のセントロイドが作成されます。

    • セントロイド 1 は、あまり混雑しておらず、レンタル期間が短い都市ステーションを示しています。
    • セントロイド 2 は、あまり混雑しておらずレンタル使用期間が長い、2 番目の都市ステーションを示しています。
    • セントロイド 3 は、市内中心部に近い混雑している都市ステーションを示しています。
    • セントロイド 4 は、移動がより長い郊外のステーションを示しています。

    自転車レンタル事業を運営している場合は、この情報を使用してビジネス上の意思決定を行うことができます。例:

    • 新しいタイプの鍵を試す必要があるとします。この実験の対象として、どのステーション クラスタを選択すればよいでしょうか。セントロイド 1、セントロイド 2、またはセントロイド 4 のステーションは最も混雑しているステーションではないので、論理的に言ってこれらを選択するのがよいでしょう。

    • 長距離用の自転車をいくつかのステーションに置くことを考えているとします。どのステーションが適しているでしょうか。セントロイド 4 は、市内中心部から離れた場所にあるステーションのグループで、最も長い利用になっています。したがって、長距離用の自転車を置くには最も適したステーションと思われます。

ML.PREDICT 関数を使用してステーションのクラスタを予測する

ML.PREDICT SQL 関数または predict BigQuery DataFrames 関数を使用して、特定のステーションが属しているクラスタを特定します。

SQL

次のクエリは REGEXP_CONTAINS 関数を使用して、station_name 列に文字列 Kennington を含むすべてのエントリを検索します。ML.PREDICT 関数は、これらの値を使用して、どのクラスタにステーションが含まれるかを予測します。

次の手順で、名前に Kennington という文字列を含むすべてのステーションのクラスタを予測します。

  1. Google Cloud コンソールで、[BigQuery] ページに移動します。

    [BigQuery] に移動

  2. クエリエディタに次のクエリを貼り付け、[実行] をクリックします。

    WITH
    hs AS (
      SELECT
        h.start_station_name AS station_name,
        IF(
          EXTRACT(DAYOFWEEK FROM h.start_date) = 1
            OR EXTRACT(DAYOFWEEK FROM h.start_date) = 7,
          'weekend',
          'weekday') AS isweekday,
        h.duration,
        ST_DISTANCE(ST_GEOGPOINT(s.longitude, s.latitude), ST_GEOGPOINT(-0.1, 51.5)) / 1000
          AS distance_from_city_center
      FROM
        `bigquery-public-data.london_bicycles.cycle_hire` AS h
      JOIN
        `bigquery-public-data.london_bicycles.cycle_stations` AS s
        ON
          h.start_station_id = s.id
      WHERE
        h.start_date
        BETWEEN CAST('2015-01-01 00:00:00' AS TIMESTAMP)
        AND CAST('2016-01-01 00:00:00' AS TIMESTAMP)
    ),
    stationstats AS (
      SELECT
        station_name,
        isweekday,
        AVG(duration) AS duration,
        COUNT(duration) AS num_trips,
        MAX(distance_from_city_center) AS distance_from_city_center
      FROM
        hs
      GROUP BY
        station_name, isweekday
    )
    SELECT *
    EXCEPT (nearest_centroids_distance)
    FROM
    ML.PREDICT(
      MODEL `bqml_tutorial.london_station_clusters`,
      (
        SELECT *
        FROM
          stationstats
        WHERE
          REGEXP_CONTAINS(station_name, 'Kennington')
      ));

結果は次のようになります。

ML.PREDICT の結果

BigQuery DataFrames

このサンプルを試す前に、BigQuery DataFrames を使用した BigQuery クイックスタートの手順に沿って BigQuery DataFrames を設定してください。詳細については、BigQuery DataFrames のリファレンス ドキュメントをご覧ください。

BigQuery に対する認証を行うには、アプリケーションのデフォルト認証情報を設定します。詳細については、ローカル開発環境の ADC の設定をご覧ください。


# Select model you'll use for predictions. `read_gbq_model` loads model
# data from BigQuery, but you could also use the `cluster_model` object
# from previous steps.
cluster_model = bpd.read_gbq_model(
    your_model_id,
    # For example: "bqml_tutorial.london_station_clusters",
)

# Use 'contains' function to filter by stations containing the string
# "Kennington".
stationstats = stationstats.loc[
    stationstats["station_name"].str.contains("Kennington")
]

result = cluster_model.predict(stationstats)

# Expected output results:   >>>results.peek(3)
# CENTROID...	NEAREST...	station_name  isweekday	 duration num_trips dist...
# 	1	[{'CENTROID_ID'...	Borough...	  weekday	  1110	    5749	0.13
# 	2	[{'CENTROID_ID'...	Borough...	  weekend	  2125      1774	0.13
# 	1	[{'CENTROID_ID'...	Webber...	  weekday	  795	    6517	0.16
#   3 rows × 7 columns