layout(ring): degenerate ring_col values break the [min_r,max_r] contract three different ways — continuous silently all-NaN, categorical ZeroDivisionError, time_ring puts NaT at 44x max_r
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 初心者へのやさしさ
- 55/100
- issue の種類
- バグ
- 明瞭さ
- 明確に書かれている
- 活発さ
- 静か
調査の方向性
Start with the cited implementations in graphistry/layout/ring/continuous.py, categorical.py, and time.py, then run the pandas reproductions in the issue to confirm the radius and failure cases. Check existing ring-layout tests for coverage. Done means degenerate continuous and categorical values and NaT timestamps no longer violate the [min_r, max_r] contract, and time_unit affects bin width as intended.
索引モデルが issue の本文から書いたものです。
説明
Found by the 2026-08 layouts/viz amplification probe. All repros on master @ 42b4f7f7d, pandas 2.3.3 / py3.12, CPU.
The three ring layouts share a contract — every node lands on a radius in [min_r, max_r] (default [100, 1000]) — and all three break it on ordinary degenerate column values. They also break it in three mutually inconsistent ways on the same input shape, which is the part worth fixing together.
Defect 1 (silent-wrong): ring_continuous_layout returns all-NaN positions for a constant column
graphistry/layout/ring/continuous.py:198-202:
198: scalar = (max_r - min_r) / (v_end - v_start)
199: r = (g._nodes[ring_col] - v_start) * scalar + min_r
v_start/v_end come from .min()/.max(), so they are numpy scalars: a constant column gives 900.0 / np.float64(0.0) = inf (RuntimeWarning only), and then 0 * inf = NaN for every row.
import pandas as pd, graphistry
e = pd.DataFrame({'s':[0,1,2],'d':[1,2,0]})
n = pd.DataFrame({'id':[0,1,2],'v':[2.0, 2.0, 2.0]})
out = graphistry.edges(e,'s','d').nodes(n,'id').ring_continuous_layout('v')
print(out._nodes[['r','x','y']])
Observed: r, x, y all NaN for every node. No warning, no error.
Expected: all nodes on one ring (the natural reading), or a typed decline.
Same for a single-node graph, and for an all-NaN ring_col. Note circle_layout asserts not x.isna().any() at circle.py:362-363 — the ring layouts have no such guard.
Sub-note: if a caller passes python ints for v_start/v_end instead, the same line raises ZeroDivisionError. Whether you get NaN or an exception depends on the scalar type.
Defect 2 (bare-crash): ring_categorical_layout ZeroDivisionError on a single distinct category
graphistry/layout/ring/categorical.py:229-230:
229: num_rings = len(set(val_to_ring.values())) - 1
230: scalar = (max_r - min_r) / num_rings
One distinct value ⇒ num_rings == 0 ⇒ ZeroDivisionError: division by zero.
import pandas as pd, graphistry
e = pd.DataFrame({'s':[0,1,2],'d':[1,2,0]})
n = pd.DataFrame({'id':[0,1,2],'cat':['a','a','a']})
graphistry.edges(e,'s','d').nodes(n,'id').ring_categorical_layout('cat') # ZeroDivisionError
Same for an all-NaN column (unique() → [nan], one category). Empty nodes give num_rings == -1, a negative scalar, and no error at all — a third behaviour for a fourth degenerate shape.
This is the identical input shape as Defect 1 ("the ring column has one distinct value"), and the two sibling layouts disagree: continuous silently NaNs, categorical hard-crashes.
It also fires straight off a GFQL filter:
g2 = g.gfql([n({'grp': 'a'})]) # survivors all share one 'grp' value
g2.ring_categorical_layout('grp') # ZeroDivisionError
Defect 3 (silent-wrong): time_ring_layout throws NaT nodes ~44x outside max_r
graphistry/layout/ring/time.py:355 does sf = s.astype('int64') on the datetime column; pandas maps NaT to -9223372036854775808, and that sentinel is then scaled as if it were a real timestamp at :369-370.
import pandas as pd, graphistry
e = pd.DataFrame({'s':[0,1,2],'d':[1,2,0]})
n = pd.DataFrame({'id':[0,1,2], 't': pd.to_datetime(['2020-01-01', None, '2022-12-01'])})
out = graphistry.edges(e,'s','d').nodes(n,'id').time_ring_layout('t')
print(out._nodes[['id','t','r','x','y']])
Observed (defaults min_r=100, max_r=1000):
id t r x y
0 0 2020-01-01 105.917808 105.917808 0.000000
1 1 NaT 43744.817562 23635.425799 36809.994714 <-- 44x max_r
2 2 2022-12-01 631.123288 -262.639960 573.878781
Expected: NaT excluded, NaN'd, or placed on a named "unknown" ring — anything inside the declared radius band. A single null timestamp blows the viewport out by ~44x and destroys zoom-to-fit for the whole graph. A null in a timestamp column is normal data.
Defect 4 (minor, same module): time_unit is dead code in find_round_bin_width
graphistry/layout/ring/time.py:61-70:
61: if time_unit is not None:
62: (unit, date_offset, td_unit) = [(x, y, z) for (x, y, z) in units if x == time_unit][0]
63:
64: for unit, date_offset, td_unit in (units):
65: if duration_seconds <= td_unit:
66: return unit, date_offset, td_unit
The for at :64 immediately rebinds all three names and returns based on duration alone, so line :62 has no effect — the user's time_unit never influences bin width. Observable as 'W', 'M', 'Y', 'C' all producing an identical r_max on the same data. Line :62's [0] is also an unguarded index (bare IndexError for an unknown unit), though in practice unit_to_timedelta at :39 gets there first with a bare KeyError (e.g. time_unit='d' — a plausible typo for the valid 'D').
Category
Defect 1: silent-wrong. Defect 2: bare-crash on valid input. Defect 3: silent-wrong (contract violation: radius outside [min_r, max_r]). Defects 1+2 together are a surface divergence between two layouts documented as siblings. Defect 4: dead code / ignored parameter.
Not exercised
cuDF arm not run: cupy in this environment cannot load libnvrtc.so.12, so every ring layout fails at import time on GPU frames for an environmental reason and no GPU statement is made. By inspection, Defect 3 diverges on GPU — cuDF's astype('int64') preserves <NA> rather than emitting the -2**63 sentinel, so pandas and cuDF would give different wrong answers for the same input. INDICATIVE, static only.
- 主要言語
- Python
- スター
- 2.6k
- フォーク
- 229
- 平均マージ
- 1日 5時間
- マージ済み PR(30日)
- 99
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートあり
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
graphistry/pygraphistry のほかの issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 62/100
graphistry/pygraphistry#2171 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
graphistry/pygraphistry#1965 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 65/100
graphistry/pygraphistry#1208 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
graphistry/pygraphistry#966 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
graphistry/pygraphistry#824 ·
メンテナーはふだん 1 日以内に返信
graphistry/pygraphistry の issue をすべて見る
似ている issue
-
documentation
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
awslabs/social-messaging-agent-runtime-toolkit#140 ·
メンテナーはふだん 1 日以内に返信
-
subtask
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
fga-eps-mds/2026.2-MeasureSoftGram-DOC#187 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 83/100
txn2/mcp-data-platform#2493 ·
メンテナーはふだん 1 日以内に返信
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
YO-WHATS-UP2/minigit#78 ·
メンテナーはふだん 3 日以内に返信
-
documentation
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
liberation-data/drivine-python#432 ·
メンテナーはふだん 1 日以内に返信