pygraphistry-gfql について
pygraphistry-gfqlは、GFQLでPyGraphistryのグラフを問い合わせるためのスキルです。チェーンリストとCypherの構文、グラフ探索、段階的なバインディング、実行エンジンの選択を扱います。
主な機能
- 01チェーンリストとCypherのクエリ
- 02パターンマッチングとグラフ探索
- 03グラフ構築とDAGバインディング
- 04Pandas、Polars、GPUエンジンの指針
活用シーン
- 01グラフパターンに合う経路の検索
- 02複数段階のグラフクエリ構築
- 03クエリ実行エンジンの選択
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ graphistry/ graphistry-skills --skill pygraphistry-gfqlインストール後、Claude Code で/pygraphistry-gfqlを入力して読み込まれたか確認します。
同梱ファイルreferences/gfql-cypher.mdreferences/gfql-engines.md
詳細
PyGraphistry GFQL
ドキュメントのルーティング(ローカル + 正規)
- まず
../pygraphistry/references/pygraphistry-readthedocs-toc.mdでルーティングします。 - セクション単位のショートカットには
../pygraphistry/references/pygraphistry-readthedocs-top-level.tsvを使用します。 - 必要なページが見つからない場合に限り、
../pygraphistry/references/pygraphistry-readthedocs-sitemap.xmlを確認します。 - 詳細ページを読み込む前に、まとめて一度だけ情報を読み取ります。
cat *や細かな読み込みを順番に繰り返すことは避けてください。 - ユーザー向けの回答では、正規の
https://pygraphistry.readthedocs.io/en/latest/...リンクを優先します。
2つの構文、1つのエントリーポイント
g.gfql() はチェーンリスト(Python AST オブジェクト)と Cypher 文字列の両方を受け付けます。引数の型から言語を自動検出します。
# Chain-list syntax (Python AST objects)
g2 = g.gfql([n({'type': 'person'}), e_forward(), n()])
# Cypher string syntax (auto-detected)
g2 = g.gfql("MATCH (p:Person)-[r:KNOWS]->(q:Person) RETURN p.name, q.name")
# Explicit language parameter (optional)
g2 = g.gfql(query_string, language="cypher")
使い分け:
- チェーンリスト:プログラムによる組み立て、動的なパラメーター化、コードからクエリを構築する場合
- Cypher:可読性、Cypher ユーザーにとっての親しみやすさ、RETURN/ORDER BY/LIMIT を使った複雑なパターンマッチング
クイックスタート — チェーンリスト
from graphistry import n, e_forward
g2 = g.gfql([
n({'type': 'person'}),
e_forward({'relation': 'transfers_to'}, min_hops=1, max_hops=3),
n({'risk': True})
])
クイックスタート — Cypher
# Simple pattern match
g2 = g.gfql("MATCH (p:Person)-[r:KNOWS]->(q:Person) WHERE p.age > 30 RETURN p.name, q.name")
# Variable-length paths
g2 = g.gfql("MATCH (a:Account)-[*1..3]->(m:Merchant) RETURN a, m")
# Parameterized queries
g2 = g.gfql(
"MATCH (n) WHERE n.score > $cutoff RETURN n.id, n.score ORDER BY n.score DESC LIMIT $top_n",
params={"cutoff": 50, "top_n": 10}
)
# Relationship type alternation
g2 = g.gfql("MATCH (a:Person)-[:KNOWS|COLLABORATES_WITH]->(b:Person) RETURN a.name, b.name")
すべての句/関数とラベル列のマッピングについては、references/gfql-cypher.md を参照してください。
GRAPH コンストラクターと Let/DAG バインディング
from graphistry import n, e_forward, let, ref
# Subgraph as a graph object (not a table)
subgraph = g.gfql("GRAPH { MATCH (a)-[r]->(b) WHERE a.risk_score > 7 }")
# Named bindings forming a DAG; ref() operates on the referenced binding's output
result = g.gfql(let({
'high_risk': n({'risk_score': {'$gt': 0.8}}),
'neighborhoods': ref('high_risk', [e_forward(max_hops=2), n()])
}), output='neighborhoods')
独立したバインディングはルートグラフに対して動作します。複数段階の DAG、ネストされた let のスコープ規則、GRAPH/USE パイプライン形式については、references/gfql-cypher.md を参照してください。
対象を絞ったパターン(重要度の高い情報)
# Edge query filtering
g2 = g.gfql([n(), e_forward(edge_query="type == 'replied_to' and submolt == 'X'"), n()])
# Same-path constraints with where + compare/col
from graphistry import col, compare
g2 = g.gfql([n(name='a'), e_forward(name='e'), n(name='b')], where=[compare(col('a', 'owner_id'), '==', col('b', 'owner_id'))])
# Traverse 2-4 hops but only return hops 3-4
g2 = g.gfql([e_forward(min_hops=2, max_hops=4, output_min_hops=3, output_max_hops=4)])
エッジ方向のバリエーション
e_forward()— 始点から終点e_reverse()— 終点から始点e_undirected()— 両方向e()— 任意の方向のエイリアス
有用性の高いパターン
g.gfql()は統一されたエントリーポイントです。チェーンリストまたは Cypher 文字列を渡します。.chain()や.hop()は絶対に使用しないでください。非推奨であり、警告が出力されます。チェーンリスト構文には常にg.gfql([...])を、Cypher にはg.gfql("MATCH ...")を使用してください。- ユーザーが GFQL を明示的に求めている場合、最終的なコード例には明示的な
.gfql(...)を含めてください。 - タスクでリモート実行/データセットが指定されている場合は、
gfql_remote(...)を使用してください。 - 制約が必要な中間マッチには、
name=ラベルを使用してください。 - ステップ間/パスの制約には、
where=[...]を使用してください。 - トラバーサルと返される範囲の指定には、
min_hops/max_hopsとoutput_min_hops/output_max_hopsを使用してください。 - 簡潔なフィルタリングには、述語(
is_in、数値/日付の述語)を使用してください。 - パフォーマンスや結果フレームの型が重要な場合は、エンジンを明示してください。
engine='auto'は Polars を選択しません。
実行エンジン:pandas、Polars、cuDF、Polars-GPU
ワークロードに適したエンジンで、同じクエリを使用します。入力フレームの型と実行エンジンは独立しており、GFQL は入力を一度変換し、選択したエンジンの型でフレームを返します。
query = "MATCH (a)-[e]->(b) WHERE a.risk_score > $cutoff RETURN b"
cpu_out = g.gfql(query, params={'cutoff': 7}, engine='polars')
gpu_out = g.gfql(query, params={'cutoff': 7}, engine='polars-gpu')
# Polars/Polars-GPU outputs are polars.DataFrame objects
nodes_pd = cpu_out._nodes.to_pandas() # only for pandas-only downstream APIs
- 有効なリテラルは
'pandas'、'cudf'、'dask'、'dask_cudf'、'polars'、'polars-gpu'、'auto'だけです。'polars-gpu'はハイフンでつなぎます。polars_gpuはエンジンではありません。 - Polars 入力グラフでは、
engine='auto'は pandas に解決されます。ネイティブのまま使用するにはengine='polars'を渡してください。 - 出力は選択したエンジンに従います。
polars/polars-gpuでは Polars、cudfでは cuDF です。.ilocやgroupby().apply()など、pandas 専用の操作を行う前に、意図的に変換してください。 polars-gpuには RAPIDS スタックが必要で、GPU で実行できるか、エラーになります。CPU に黙ってフォールバックすることはありません。
Polars エンジンでの分析
UMAP、ハイパーグラフ、レイアウト、compute_cugraph など、グラフ全体を対象とする call() による分析は、Polars のネイティブ操作ではありません。デフォルトの call_mode='auto' では、GFQL はエンジン外(Polars の場合は pandas、Polars-GPU の場合は cuDF)に処理をブリッジし、結果を元に戻します。ベンチマーク、メモリ、実行の制約に反するエンジン外へのブリッジを避ける必要がある場合は、strict モードを使用してください。
from graphistry.compute.gfql.lazy import set_call_mode
set_call_mode('strict') # reject an off-engine analytic before it runs
try:
result = g.gfql(query, engine='polars')
except NotImplementedError as exc:
... # strict mode declined an off-engine analytic
gfql() は strict= や call_mode= 引数を取りません。モードはプロセス単位で、set_call_mode('auto'|'strict') または GFQL_POLARS_CALL_MODE で設定します(Python のオーバーライド > 環境変数 > デフォルトの 'auto')。各呼び出し時に最新の値が読み取られます。strict モードでは警告や RuntimeError ではなく、NotImplementedError が発生します。プロセス全体に適用されるため、1 つのステップだけ strict にする場合は、スコープを限定し、finally: で元に戻してください(references/gfql-engines.md)。
polars-gpu の分析は GPU 専用で、GPU/cuDF スタックが利用できない場合は、ホスト側の pandas に処理を移さず、処理できないことを示します。
エンジンの調整設定
graphistry.compute.gfql.lazy にはプロセス単位の設定が 3 つあり、いずれも Python のオーバーライド > 環境変数 > デフォルト の順で解決され、インポート時に固定されるのではなく、collect ごとに最新の値が読み取られます。
| 設定 | 値 | デフォルト | 環境変数 |
|---|---|---|---|
set_call_mode | 'auto'、'strict' | 'auto' | GFQL_POLARS_CALL_MODE |
set_gpu_executor | 'in-memory'、'streaming' | 'in-memory' | GFQL_POLARS_GPU_EXECUTOR |
set_cpu_streaming | True、False | False | GFQL_POLARS_CPU_STREAMING |
現在の値は call_mode()、gpu_executor()、cpu_streaming() で確認できます。セッターに None を渡すと、環境変数またはデフォルトの値に戻ります。'polars' と 'polars-gpu' は、2 つの collect ターゲットを持つ 1 つの遅延エンジンです。そのため、プランは 1 回だけ構築され、collect も 1 回だけ実行されます。この転送を 1 回に抑える設計が、GPU の効果を引き出します。
collectを 1 回にすれば、ホストからデバイスへの転送(H2D)の繰り返しを避けられます。 操作ごとに eagercollectを行うと、毎回フレームをデバイスにコピーし直すことになり、ベンチマークでは GPU の性能低下につながりました。- 設定は Python のオーバーライド > 環境変数 > デフォルト の順で解決され、インポート時に固定されるのではなく、
collectごとに最新の値が読み取られます。
物理インデックス:シード検索
GFQL には、シードを使った探索向けに、必要に応じて利用できる隣接/ノード ID インデックス(graphistry.compute.gfql.index)が付属しています。インデックスが役立つかどうかは、次の 2 つのルールで決まります。
- クエリの形式 — インデックスを参照するのはチェーン/ホップ形式のみです。 実測では、シード付きチェーンは 8.90ms → 1.68ms(5.3 倍) に短縮されました。一方、Cypher の 2 つの表記はいずれも参照されず、改善もありませんでした。インデックスによる高速化が必要な場合は、チェーン形式で記述してください。
- フロンティアのサイズはエンジンに応じて判定されます。 プランナーは、フロンティアがソースキーの種類数に占める割合を基準に、インデックスとスキャンのどちらを使うかを決めます。閾値は pandas で約 0.5、polars/cuDF/GPU で約 0.02 です。ベクトル化エンジンではスキャンが十分に高速なため、インデックスが有利なのはシードの選択性が非常に高い場合に限られます。閾値を超えるとスキャンに切り替わるため、インデックスによって処理が遅くなることはありません。
from graphistry.compute.gfql.index import create_index, index_trace
gi = create_index(g, 'edge_out_adj', engine='polars') # edge_out_adj | edge_in_adj | node_id
with index_trace() as steps:
out = gi.gfql([n({'id': 'acct-42'}), e_forward(), n()], engine='polars')
steps[0]['path'] # confirm 'index', do not assume it
index_policy= は、call mode とは異なり、gfql() の呼び出しごとに指定できるキーワードです。値は 'use'(デフォルト。常駐インデックスを使用し、コスト判定を適用)、'auto'(必要に応じて構築)、'force'(閾値判定を省略)、'off' です。常駐インデックスがない場合、'use' は何も通知せずにスキャンします。インデックスの DDL、トレースフィールド、コスト閾値のオーバーライドについては、references/gfql-engines.md を参照してください。
性能を考慮したエンジンの選択
測定していない速度向上をうたわないでください。
- pandas → polars:およそ 5 万~10 万行を超える場合は効果が期待できます。それより少ないと変換の負荷が上回る場合があります。シード付き 1 ホップでは、実測で約 2 倍でした。
- polars → polars-gpu は常に有利とは限りません。 NVIDIA GB10 での実測では、10 万行で 0.83 倍(CPU より低速)、100 万行で 1.41 倍、500 万行で 0.98 倍でした。右肩上がりではなく、ばらつきがあります。800 万エッジでは
cudfが両方を上回りました。「polars-gpu は cudf より速い」という一般論は決して述べないでください。 set_cpu_streaming(True)はオプトインで、低速になることがあります。小規模または対話的なサイズでは約 0.86 倍です。大規模なバッチ CPU 処理向けであり、名前から受ける印象ほど速いわけではありません。- 測定結果の全表は
references/gfql-engines.mdを参照してください。
エンジンの選び方 — 判断手順
順番に確認し、判断がついた時点で終了します。
- Polars で処理が拒否されますか? その処理を
engine='pandas'で実行し、エンジンとして pandas を報告します。 - フレームはすでにどこにありますか?
polars-gpuは ホスト上の polars フレームを取り込むため、すでにデバイス上にある cuDF フレームにはengine='cudf'が適しています。ホスト上の Polars フレームには'polars'/'polars-gpu'が適しています。 - シード付きの検索で、フロンティアは小さいですか? インデックスを作成してチェーン形式を使います。エンジンの選択とは無関係に、利用できる単独の改善として最大の効果(5.3x)が得られます。
- CPU: 複雑な処理には
pandasよりpolarsを優先します。 - GPU: ワークロードが十分に大きい場合にのみ使い、GPU エンジンは名称ではなく測定結果に基づいて選びます。
数ミリ秒未満の処理では、エンジンの選択による違いはほとんどありません。インデックスとクエリの形状のほうが重要です。
同等性を保てない場合は拒否する:回避策をでっち上げない
まず誤ったラベル付けを拒否し、そのうえで問題を解決します。 ダッシュボードを緑色に保つため、ベンチマークの比較可能性を保つため、または API 契約を安定させるために、pandas で実行した処理を engine='polars' と報告し続けるようユーザーから求められた場合は、コードを書く前に断ります。内部で pandas を実行しながら engine='polars' を公開するラッパーは、別のフィールドに実際のエンジンを記録していても誤ったラベル付けです。人々が目にするのは主ラベルだからです。このスキルで実装するのではなく、異議を唱えるべき依頼はこれだけです。
提案できる誠実な代替案は、engine='pandas' で処理を実行して pandas と報告すること、または拒否される機能を避けてパイプラインを Polars ネイティブに保つことです。
そのうえで、仕組みについて説明します。Polars エンジンでの走査、フィルター、行操作は、同等性を保つか、NotImplementedError を発生させるかのいずれかです。
- エンジンが pandas に暗黙にフォールバックすることはありません。隠れた橋渡しを行うと、pandas の性能を Polars の性能として誤って報告することになるためです。
- 未対応の機能では、警告ではなく
NotImplementedErrorが発生します(RuntimeErrorではありません)。 - 現在、処理が拒否される機能:有向でない
min_hops>1、直接のhop(min_hops>1)(chain()/gfql()を使用してください)、複数エンティティのrows(binding_ops=…)、エンティティをまたぐ同一パスのWHERE、特殊な式(CASE/list/map/temporal)。全リストはreferences/gfql-engines.mdを参照してください。
エンジンへの変換には、リポジトリ全体で採用されている validate/warn の規約が適用されます。Arrow で表現できない混合型のオブジェクト列の場合:
validate | 動作 |
|---|---|
'strict' | エラーが発生します(polars では NotImplementedError)。列は変更されません |
'autofix' | 列が文字列に変換され、警告が出力されます — 警告を読まなければ、データは暗黙に書き換えられます |
通知なしにデータが変更されてはならないジョブでは、'strict' を使用してください。
リモートモード
# Remote with chain-list
rg = graphistry.bind(dataset_id='my-dataset')
res = rg.gfql_remote([n(), e_forward(), n()], engine='auto')
# Remote with Cypher string
res = rg.gfql_remote("MATCH (n:Person)-[r]->(m) WHERE n.risk_level = 'critical' RETURN n, r, m")
# Remote with Let/DAG
res = rg.gfql_remote(let({...}))
# Remote slim payload (only required columns)
res = rg.gfql_remote([n(), e_forward(), n()], output_type='nodes', node_col_subset=['node_id', 'time'])
# Post-process on remote side when you want trimmed transfer payloads
res = rg.python_remote_table(lambda g: g._edges[['src', 'dst']].head(1000))
検証と安全性
- ユーザー由来のクエリ断片を実行前に検証します。
- 時間条件を適用する前に、日時列を正規化します。
- リモートで結果を転送する際は、対象となる列を少数に絞ります。
- Cypher を事前検証します:
from graphistry.compute.gfql.cypher import parse_cypher, compile_cypher
正式なドキュメント
- GFQL インデックス:https://pygraphistry.readthedocs.io/en/latest/gfql/index.html
- GFQL の概要:https://pygraphistry.readthedocs.io/en/latest/gfql/overview.html
- GFQL クイックリファレンス:https://pygraphistry.readthedocs.io/en/latest/gfql/quick.html
- 述語クイックリファレンス:https://pygraphistry.readthedocs.io/en/latest/gfql/predicates/quick.html
- GFQL リモートモード:https://pygraphistry.readthedocs.io/en/latest/gfql/remote.html
- GFQL 検証:https://pygraphistry.readthedocs.io/en/latest/gfql/validation/index.html
- エンジンガイド:https://pygraphistry.readthedocs.io/en/latest/gfql/engines.html
- GFQL とローダー/AI のパターン:https://pygraphistry.readthedocs.io/en/latest/gfql/combo.html
- Cypher 構文ガイド:https://pygraphistry.readthedocs.io/en/latest/gfql/cypher.html
- Cypher と GFQL の対応表:https://pygraphistry.readthedocs.io/en/latest/gfql/spec/cypher_mapping.html