PageRank で公開 API を選ぶと失敗する — 実測で分かった 4 つの誤り
コード知識ベースを圧縮する際、重要度スコアで残す API を選ぶ手法は失敗しました。pandas の groupby が丸ごと消えた原因と、保護層による解決を実測データとともに解説します。
公開日: 2026-08-07 静的解析圧縮ランキングPageRank
コードの知識ベースを圧縮するとき、避けて通れない問題があります。 全部は入らないので、何かを捨てなければならない。
では何を捨てるか。素直に考えれば「重要でないもの」です。そして重要度を測る 定番の手法が PageRank です。呼び出しグラフ上でよく参照されるシンボルほど 重要である——直感的で、実装も簡単です。
これは失敗しました。しかも 4 回連続で。
症状: pandas の groupby が消えた
901 のオープンソースリポジトリから抽出した 145 万シンボルを圧縮した結果、 DataFrameGroupBy.aggregate が消えていました。Series.groupby も、 SeriesGroupBy.aggregate もです。
pandas を使う人が最も知りたい API が、丸ごと失われていたことになります。 一方で生き残っていたのは、こういうものでした。
pandas.generate_pxi.main
pandas.generate_version.write_version_info
pandas._config.config.get_default_val
ビルドスクリプトと内部設定です。利用者が呼ぶことのないコードが優先的に 残り、主力 API が捨てられていました。
原因: PageRank は「呼ばれる回数」しか測らない
理由は考えてみれば当然でした。
PageRank はグラフ上で「どれだけ参照されるか」を測ります。しかし 公開 API のエンドポイントは、ライブラリ内部からはほとんど呼ばれません。 呼ぶのは外部の利用者コードだからです。そして利用者コードは解析対象に 含まれていません。
実測してみると、決定的な数字が出ました。
| シンボル | PageRank |
|---|---|
DataFrameGroupBy.aggregate | 3.37e-05 |
| 生き残った平凡なメソッド | 3.37e-05 |
Series クラス | 桁が 2 つ上 |
3.37e-05 は孤立ノードの最低値です。つまり pandas の大半のシンボルは PageRank 上で区別がついていませんでした。 順位は事実上、推定コストの 端数で決まっていたのです。意味のない順序でした。
失敗した 4 回の試行
最初は重み付けの調整で解けると考えました。公開 API にボーナスを与え、 docstring があれば加点し、型注釈があればさらに加点する。
結果は以下の通りです。
| 試行 | 手法 | API 保持率 |
|---|---|---|
| 開始時 | PageRank のみ | 90.3% |
| 1 回目 | 公開・doc・型注釈にボーナス | 81% |
| 2 回目 | グラフ価値と API 価値の max を取る | 81% |
| 3 回目 | 加算方式に変更 | 81% |
| 4 回目 | コスト関数を調整 | 77.4% |
触るたびに悪化しました。 これは重み調整という手法そのものが 間違っている証拠です。
途中で分かった細かい罠も記録しておきます。docstring の「長さ」を 品質の指標にしたところ、かえって精度が落ちました。pandas の docstring は 一文目が簡潔で、"Aggregate using one or more operations." はわずか 39 文字です。 長さで測ると、丁寧に書かれた優良な docstring ほど罰されるという 逆転が起きていました。
解決: 順位付けをやめ、保護層を作る
発想を変えました。順位付けが縮退している領域で順序をいじっても、 重要な API が残る保証は永久に得られません。必要なのは順位ではなく 保証です。
そこで刈り取りの母集団から外すことにしました。
def _is_surface_api(self, s):
"""予算に関わらず最後まで守る対象か"""
if not s.public:
return False
if not (s.gist or s.doc):
return False # docstring 無しは表 API とみなさない
if s.name.startswith("__") and s.name.endswith("__"):
return False # dunder は鋳型で復元できる
if set(s.qual.split(".")) & _NON_API_MODULES:
return False # conftest 等のテスト補助は除外
return True
守る条件はシンプルです。公開されていて、docstring があるもの。 これは「作者が意図して利用者に見せている API」の定義とほぼ一致します。
結果は一発で解決しました。
| 指標 | 修正前 | 修正後 |
|---|---|---|
| API 保持率 | 90.3% | 100% |
| ノイズ混入 | 84 件 | 0 件 |
10 ライブラリ 31 個の代表 API すべてが、圧縮後も残るようになりました。 sqlalchemy は 9,902 シンボルを 2,220 まで削っても select / join / Column が無傷です。
教訓
PageRank が縮退している領域では、順位付けではなく「何を守るか」を 決めるほうが正しい。
これは一般化できる話だと考えています。スコアで並べる手法は、スコアが 意味のある差を持つ範囲でしか機能しません。差がない集団を並べ替えても、 出てくるのはノイズの順序です。
そしてもう一つ。測定手段を先に作るべきでした。 4 回の失敗のうち 最初の 3 回は、そもそも改善しているか悪化しているか分からないまま 進めていました。回帰テストを書いてから、初めて「触るたびに悪化している」 という事実が見えたのです。
補足: 触ってはいけない定数
同じ罠を踏まないよう、実装には注意書きを残しています。
frac = 0.20(1 ラウンドの刈り幅)—— 0.25 に上げると境界の表 API を
巻き込み、保持率が 100% から 90% に落ちます。
- docstring の長さを条件にしない —— 前述の通り、優良な docstring が
罰されます。
圧縮率を上げるだけなら簡単です。全部捨てれば 0 バイトになります。 難しいのは、利用者が実際に知りたいものを残すことです。