BigQuery DataFrames のパフォーマンスを最適化する

BigQuery DataFrames は、pandas 互換の API を使って BigQuery のデータを分析、変換するのに役立ちます。データ処理をより高速かつコスト効率よくするために、パフォーマンスを高めるいくつかの手法を利用できます。

このドキュメントでは、パフォーマンスを最適化する次の方法について説明します。

部分順序モードを使用する

BigQuery DataFrames には順序モードという機能があり、ウィンドウ関数や結合などのオペレーションに特定の行の順序を強制します。ordering_mode プロパティを設定することで、順序モードを指定できます。strict(厳密順序モード。デフォルト)または partial(部分順序モード)のいずれかを選択可能です。partial に設定すると、クエリの効率を高めることができます。

部分順序モードは厳密順序モードとは異なります。厳密順序モードでは、すべての行が特定の順序で並べ替えられます。この完全な順序付けにより、BigQuery DataFrames は pandas 都よりスムーズに連携でき、DataFrame.iloc プロパティを使用して行番号順にアクセスできるようになります。ただし、完全な順序付けとデフォルトの連番インデックスのため、列や行にフィルタを設定しても、スキャンされるデータ量は減少しません。この制約は、フィルタを read_gbq 関数と read_gbq_table 関数のパラメータとして適用した場合にのみ回避されます。DataFrame 内のすべての行を並べ替えるため、BigQuery DataFrames はすべての行のハッシュを作成します。このオペレーションにより、行フィルタと列フィルタが無視され、データ全体がスキャンされる可能性があります。

部分順序モードでは、BigQuery DataFrames がすべての行に対して完全な順序を作ることを停止し、DataFrame.iloc プロパティのような完全な順序を必要とする機能が無効になります。また、部分順序モードでは、DefaultIndexKind クラスが連番ではなく null インデックスに設定されます。

部分順序モードで DataFrame オブジェクトをフィルタリングする場合、BigQuery DataFrames は連番インデックス上でどの行が欠落しているかを計算せず、インデックスに基づく自動的なデータ結合も行いません。これらのアプローチにより、クエリの効率を高めることができます。ただし、デフォルトの厳密順序モードと部分順序モードのどちらを使う場合でも、BigQuery DataFrames API は使い慣れた pandas API のように動作します。

部分順序モードでも厳密な順序モードでも、使用した BigQuery リソースに対して料金が発生します。ただし、大規模なクラスタ化テーブルとパーティション分割テーブルを扱う場合、部分順序モードを使用するとコストを削減できることがあります。これは、クラスタ列やパーティション列に行フィルタをかけることで、処理されるデータ量が減少するためです。

部分順序モードを有効にする

部分順序を使用するには、次のコードサンプルに示すように、BigQuery DataFrames で他のオペレーションを実行する前に ordering_mode プロパティを partial に設定します。

import bigframes.pandas as bpd

bpd.options.bigquery.ordering_mode = "partial"

部分順序モードには連番インデックスが存在しないため、関連のない BigQuery DataFrames オブジェクト同士を暗黙的に結合することはできません。代わりに、DataFrame.merge メソッドを明示的に呼び出して、異なるテーブル式から派生した 2 つの BigQuery DataFrame を結合する必要があります。

Series.unique() 機能と Series.drop_duplicates() 機能は、部分順序モードでは動作しません。代わりに、次の例に示すように、groupby メソッドを使用して一意の値を検索します。

# Avoid order dependency by using groupby instead of drop_duplicates.
unique_col = df.groupby(["column"], as_index=False).size().drop(columns="size")

部分順序モードでは、DataFrame.head(n) 関数と Series.head(n) 関数の出力は実行するたびに異なる場合があります。小さなランダムのデータサンプルをダウンロードするには、DataFrame.peek() または Series.peek() メソッドを使用してください。

ordering_mode = "partial" プロパティを使用する詳細なチュートリアルについては、Analyzing package downloads from PyPI with BigQuery DataFrames をご覧ください。

トラブルシューティング

部分順序モードの BigQuery DataFrame では、順序やインデックスが存在しない場合があるため、一部の pandas 互換メソッドを使用すると次のような問題が発生する可能性があります。

順序必須エラー

一部の機能(DataFrame.head() 関数や DataFrame.iloc 関数など)では、順序が必須です。順序が必要な機能の一覧については、サポートされている pandas API で「順序が必要」列をご覧ください。

オブジェクトに順序がない場合、オペレーションは失敗し、次のような OrderRequiredError メッセージが表示されます。OrderRequiredError: Op iloc requires an ordering. Use .sort_values or .sort_index to provide an ordering.

エラー メッセージにあるように、DataFrame.sort_values() メソッドを使用して 1 列以上で並び替えを行うことで順序を指定できます。また、DataFrame.groupby() のような他のメソッドでは、グループ化キーに基づいて暗黙的に完全な順序が提供されます。

すべての行に対して完全に安定した順序が設定されていない場合、後続のオペレーションで次のような AmbiguousWindowWarning メッセージが表示されることがあります。AmbiguousWindowWarning: Window ordering may be ambiguous, this can cause unstable results.

結果が毎回同じでなくても問題ない場合、あるいは指定した順序が完全なものであることを手動で確認できる場合は、次の方法で AmbiguousWindowWarning メッセージを非表示にできます。

import warnings

import bigframes.exceptions

warnings.simplefilter(
    "ignore", category=bigframes.exceptions.AmbiguousWindowWarning
)

null インデックス エラー

DataFrame.unstack() プロパティや Series.interpolate() プロパティといった機能にはインデックスが必要です。インデックスが必要な機能の一覧については、サポートされている pandas APIインデックスが必要の列をご覧ください。

部分順序モードでインデックスを必要とするオペレーションを使用すると、次のような NullIndexError メッセージが生成されます。NullIndexError: DataFrame cannot perform interpolate as it has no index. Set an index using set_index.

エラー メッセージにあるように、DataFrame.set_index() メソッドを使用して 1 列以上で並び替えを行うことでインデックスを指定できます。また、DataFrame.groupby() のような他のメソッドでは、グループ化キーに基づいて暗黙的にインデックスが提供されます。ただし、as_index=False パラメータが設定されている場合は除きます。

高コストのオペレーション後に結果をキャッシュに保存する

BigQuery DataFrames はオペレーションの内容をローカルに保存し、特定の条件が満たされるまでクエリの実行を遅延させます。そのため、同じオペレーションが異なるクエリで複数回実行される可能性があります。

コストのかかるオペレーションの繰り返しを避けるため、次の例に示すように、cache() メソッドを使用して中間結果を保存します。

# Assume you have 3 large dataframes "users", "group" and "transactions"

# Expensive join operations
final_df = users.join(groups).join(transactions)
final_df.cache()
# Subsequent derived results will reuse the cached join
print(final_df.peek())
print(len(final_df[final_df["completed"]]))
print(final_df.groupby("group_id")["amount"].mean().peek(30))

このメソッドは、結果を保存するための一時的な BigQuery テーブルを作成します。この一時テーブルの BigQuery でのストレージに対して料金が発生します。

peek() メソッドを使用してデータをプレビューする

BigQuery DataFrames には、データをプレビューするための 2 つの API メソッドが用意されています。

  • peek(n)n 行のデータを返します。ここで、n は行数です。
  • head(n) は、コンテキストに応じて、データの最初の n 行を返します。ここで、n は行数です。

head() メソッドは、データの順序が重要な場合にのみ使用します(列の 5 つの最大値を取得する場合など)。その他の場合は、次のコードサンプルに示すように、peek() メソッドを使用して、より効率的にデータを取得します。

import bigframes.pandas as bpd

# Read the "Penguins" table into a dataframe
df = bpd.read_gbq("bigquery-public-data.ml_datasets.penguins")

# Preview 3 random rows
df.peek(3)

peek() メソッドを使用して、部分順序モードを使用しながら、小さなランダムのデータサンプルをダウンロードすることもできます。

repr() データの取得を遅延させる

ノートブックまたは IDE デバッガで、BigQuery DataFrames の repr() メソッドを呼び出すことができます。ただし、この呼び出しにより、実際のデータを取得する head() 呼び出しがトリガーされます。このデータ取得は、反復的なコーディングやデバッグを遅くする可能性があり、さらにコストが発生することもあります。

repr() メソッドがデータを取得しないようにするには、次の例に示すように repr_mode 属性を "deferred" に設定します。

import bigframes.pandas as bpd

bpd.options.display.repr_mode = "deferred"

遅延モードでは、明示的な peek() 呼び出しと head() 呼び出しでのみデータをプレビューできます。

次のステップ