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 天内回复
还没有人认领这个 Issue。
评估
- 难度
- 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 天 3 小时
- 30 天内合并 PR
- 61
环境准备
- 没有 Dockerfile 或 Docker Compose 文件
- 有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 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
-
Wayback Links未关闭
难度 2/5 1-3 小时 新手友好度 82/100
chnm/teachinghistory-hugo#53 ·
-
gpt-oss "Forcing a tool call" row no longer reproduces: tool_choice is now honoured on every surface未关闭
难度 2/5 1-3 小时 新手友好度 83/100
aws-samples/sample-per-model-bedrock#840 ·
维护者通常 1 天内回复
-
难度 1/5 1 小时以内 新手友好度 85/100
521xueweihan/HelloGitHub#4318 ·
-
难度 2/5 1-3 小时 新手友好度 72/100
DiamondLightSource/eiger-correction#2 · 1 条评论 ·
-
难度 2/5 1-3 小时 新手友好度 68/100
django-cms/djangocms-frontend#413 ·