Skip to content

Commit aee8001

Browse files
committed
Remove from_geom, from_stat, and to_layer in favor of layer(geom=...) and layer(stat=...)
Extract _lookup_stat for pure spec→class/instance resolution and add stat-first detection in layer.__init__ so that layer(stat="bin") derives the correct geom. This makes from_geom, from_stat, and to_layer redundant — all callers now use layer() directly.
1 parent ce9793b commit aee8001

8 files changed

Lines changed: 84 additions & 122 deletions

File tree

plotnine/geoms/annotate.py

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
from typing import Any
1616

1717
from plotnine import ggplot
18-
from plotnine.layer import layer
1918

2019

2120
class annotate:
@@ -135,16 +134,7 @@ def __radd__(self, other: ggplot) -> ggplot:
135134
"""
136135
Add to ggplot
137136
"""
138-
other += self.to_layer() # Add layer
139-
return other
140-
141-
def to_layer(self) -> layer:
142-
"""
143-
Make a layer that represents this annotation
137+
from ..layer import layer
144138

145-
Returns
146-
-------
147-
out : layer
148-
Layer
149-
"""
150-
return self._annotation_geom.to_layer()
139+
other += layer(geom=self._annotation_geom)
140+
return other

plotnine/geoms/geom.py

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -430,20 +430,9 @@ def __radd__(self, other: ggplot) -> ggplot:
430430
:
431431
ggplot object with added layer.
432432
"""
433-
other += self.to_layer() # Add layer
433+
other += layer(geom=self)
434434
return other
435435

436-
def to_layer(self) -> layer:
437-
"""
438-
Make a layer that represents this geom
439-
440-
Returns
441-
-------
442-
:
443-
Layer
444-
"""
445-
return layer.from_geom(self)
446-
447436
def handle_na(self, data: pd.DataFrame) -> pd.DataFrame:
448437
"""
449438
Remove rows with NaN values

plotnine/layer.py

Lines changed: 63 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ class layer:
7878

7979
def __init__(
8080
self,
81-
geom: geom | type[geom] | str = "blank",
81+
geom: geom | type[geom] | str | None = None,
8282
stat: stat | type[stat] | str | None = None,
8383
*,
8484
mapping: aes | None = None,
@@ -89,15 +89,31 @@ def __init__(
8989
raster: bool = False,
9090
**kwargs: Any,
9191
):
92+
# Stat-first: derive geom from stat's default
93+
if stat is not None:
94+
stat_ref = _lookup_stat(stat)
95+
if isinstance(stat_ref, type):
96+
geom = stat_ref.DEFAULT_PARAMS.get("geom", "blank")
97+
else:
98+
geom = stat_ref.params.get("geom", "blank")
99+
# Forward stat instance's kwargs to the geom
100+
if mapping is None and data is None and not kwargs:
101+
mapping = stat_ref._raw_kwargs.get("mapping")
102+
data = stat_ref._raw_kwargs.get("data")
103+
kwargs = {
104+
k: v
105+
for k, v in stat_ref._raw_kwargs.items()
106+
if k not in ("mapping", "data")
107+
}
108+
109+
if geom is None:
110+
geom = "blank"
111+
92112
_geom = _resolve_geom(geom, mapping, data, kwargs)
93113
_stat = _resolve_stat(stat, _geom)
94114
_pos = _resolve_position(position, _geom)
95115
self._verify_arguments(_geom, _stat)
96116

97-
# Set back-references for pipeline compat
98-
_geom._stat = _stat # pyright: ignore[reportAttributeAccessIssue]
99-
_geom._position = _pos # pyright: ignore[reportAttributeAccessIssue]
100-
101117
# Layer params: prefer explicit kwargs, fall back to
102118
# geom._raw_kwargs, then geom.DEFAULT_PARAMS
103119
raw = _geom._raw_kwargs
@@ -121,56 +137,6 @@ def __init__(
121137
self.position = _pos
122138
self.zorder = 0
123139

124-
@staticmethod
125-
def from_geom(geom: geom) -> layer:
126-
"""
127-
Create a layer given a [](`~plotnine.geoms.geom`)
128-
129-
Parameters
130-
----------
131-
geom :
132-
`geom` from which a layer will be created
133-
134-
Returns
135-
-------
136-
:
137-
Layer that represents the specific `geom`.
138-
"""
139-
return layer(geom=geom)
140-
141-
@staticmethod
142-
def from_stat(stat: stat) -> layer:
143-
"""
144-
Create a layer given a [](`~plotnine.stats.stat`)
145-
146-
Parameters
147-
----------
148-
stat :
149-
`stat` from which a layer will be created
150-
151-
Returns
152-
-------
153-
:
154-
Layer that represents the specific `stat`.
155-
"""
156-
from .geoms.geom import geom as geom_cls
157-
158-
name = stat.params.get("geom", "blank")
159-
160-
if isinstance(name, geom_cls):
161-
return layer(geom=name)
162-
163-
if isinstance(name, type) and issubclass(name, geom_cls):
164-
klass = name
165-
elif isinstance(name, str):
166-
if not name.startswith("geom_"):
167-
name = f"geom_{name}"
168-
klass = Registry[name]
169-
else:
170-
raise PlotnineError(f"Unknown geom of type {type(name)}")
171-
172-
return layer(geom=klass(stat=stat, **stat._raw_kwargs))
173-
174140
@staticmethod
175141
def _verify_arguments(geom: geom, stat: stat) -> None:
176142
"""
@@ -683,6 +649,43 @@ def _resolve_geom(
683649
return klass(mapping, data, **kwargs)
684650

685651

652+
def _lookup_stat(
653+
stat_spec: stat | type[stat] | str,
654+
) -> stat | type[stat]:
655+
"""
656+
Look up a stat specification without instantiation
657+
658+
Parameters
659+
----------
660+
stat_spec :
661+
A stat instance, class, or string name.
662+
663+
Returns
664+
-------
665+
:
666+
The stat instance or class.
667+
"""
668+
from .stats.stat import stat as stat_cls
669+
670+
# Duck-type guard for module reloads
671+
if not isinstance(stat_spec, type) and hasattr(stat_spec, "compute_layer"):
672+
return stat_spec # type: ignore[return-value]
673+
674+
if isinstance(stat_spec, stat_cls):
675+
return stat_spec
676+
677+
if isinstance(stat_spec, type) and issubclass(stat_spec, stat_cls):
678+
return stat_spec
679+
680+
if isinstance(stat_spec, str):
681+
name = stat_spec
682+
if not name.startswith("stat_"):
683+
name = f"stat_{name}"
684+
return Registry[name]
685+
686+
raise PlotnineError(f"Unknown stat of type {type(stat_spec)}")
687+
688+
686689
def _resolve_stat(
687690
stat_spec: stat | type[stat] | str | None,
688691
geom_obj: geom,
@@ -703,24 +706,13 @@ def _resolve_stat(
703706
if stat_spec is None:
704707
stat_spec = geom_obj.params["stat"]
705708

706-
# Duck-type guard for module reloads
707-
if not isinstance(stat_spec, type) and hasattr(stat_spec, "compute_layer"):
708-
return stat_spec # type: ignore[return-value]
709-
710-
if isinstance(stat_spec, stat_cls):
711-
return stat_spec
709+
result = _lookup_stat(stat_spec) # type: ignore[arg-type]
712710

713-
if isinstance(stat_spec, type) and issubclass(stat_spec, stat_cls):
714-
klass = stat_spec
715-
elif isinstance(stat_spec, str):
716-
name = stat_spec
717-
if not name.startswith("stat_"):
718-
name = f"stat_{name}"
719-
klass = Registry[name]
720-
else:
721-
raise PlotnineError(f"Unknown stat of type {type(stat_spec)}")
711+
if isinstance(result, stat_cls):
712+
return result
722713

723-
# Filter geom's raw kwargs to stat-relevant keys
714+
# It's a class — instantiate with filtered geom kwargs
715+
klass = result
724716
kwargs = geom_obj._raw_kwargs
725717
valid_kwargs = (
726718
klass.aesthetics() | klass.DEFAULT_PARAMS.keys()

plotnine/stats/stat.py

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -346,16 +346,5 @@ def __radd__(self, other: ggplot) -> ggplot:
346346
out :
347347
ggplot object with added layer
348348
"""
349-
other += self.to_layer() # Add layer
349+
other += layer(stat=self)
350350
return other
351-
352-
def to_layer(self) -> layer:
353-
"""
354-
Make a layer that represents this stat
355-
356-
Returns
357-
-------
358-
out :
359-
Layer
360-
"""
361-
return layer.from_stat(self)

tests/test_geom.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,18 +47,18 @@ class geom_abc(geom):
4747
DEFAULT_PARAMS = {"stat": "identity", "position": "identity"}
4848

4949
with pytest.raises(PlotnineError):
50-
geom_abc(do_the_impossible=True).to_layer()
50+
layer(geom=geom_abc(do_the_impossible=True))
5151

5252

5353
def test_geom_from_stat():
5454
stat = stat_identity(geom="point")
55-
assert isinstance(layer.from_stat(stat).geom, geom_point)
55+
assert isinstance(layer(stat=stat).geom, geom_point)
5656

5757
stat = stat_identity(geom="geom_point")
58-
assert isinstance(layer.from_stat(stat).geom, geom_point)
58+
assert isinstance(layer(stat=stat).geom, geom_point)
5959

6060
stat = stat_identity(geom=geom_point())
61-
assert isinstance(layer.from_stat(stat).geom, geom_point)
61+
assert isinstance(layer(stat=stat).geom, geom_point)
6262

6363
stat = stat_identity(geom=geom_point)
64-
assert isinstance(layer.from_stat(stat).geom, geom_point)
64+
assert isinstance(layer(stat=stat).geom, geom_point)

tests/test_layers.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,15 @@ def test_addition(self):
4242
assert _get_colors(p2) == colors
4343

4444
# Real layers
45-
lyrs = Layers(layer.from_geom(obj) for obj in self.lyrs)
45+
lyrs = Layers(layer(geom=obj) for obj in self.lyrs)
4646
p3 = p + lyrs
4747
assert _get_colors(p3) == colors
4848

4949
p += self.lyrs
5050
assert _get_colors(p) == colors
5151

5252
with pytest.raises(PlotnineError):
53-
geom_point() + layer.from_geom(geom_point())
53+
geom_point() + layer(geom=geom_point())
5454

5555
with pytest.raises(PlotnineError):
5656
geom_point() + self.lyrs
@@ -77,7 +77,7 @@ def __init__(self, obj):
7777
self.obj = obj
7878

7979
def __radd__(self, other):
80-
other.layers.insert(0, self.obj.to_layer())
80+
other.layers.insert(0, layer(geom=self.obj))
8181
return other
8282

8383
p = (

tests/test_position.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
stage,
2727
)
2828
from plotnine.exceptions import PlotnineError
29+
from plotnine.layer import layer
2930

3031
n = 6
3132
m = 10
@@ -240,16 +241,16 @@ def test_jitterdodge():
240241

241242

242243
def test_position_from_geom():
243-
lyr = geom_point(position="jitter").to_layer()
244+
lyr = layer(geom=geom_point(position="jitter"))
244245
assert isinstance(lyr.position, position_jitter)
245246

246-
lyr = geom_point(position="position_jitter").to_layer()
247+
lyr = layer(geom=geom_point(position="position_jitter"))
247248
assert isinstance(lyr.position, position_jitter)
248249

249-
lyr = geom_point(position=position_jitter()).to_layer()
250+
lyr = layer(geom=geom_point(position=position_jitter()))
250251
assert isinstance(lyr.position, position_jitter)
251252

252-
lyr = geom_point(position=position_jitter).to_layer()
253+
lyr = layer(geom=geom_point(position=position_jitter))
253254
assert isinstance(lyr.position, position_jitter)
254255

255256

tests/test_stat.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from plotnine.data import mtcars
66
from plotnine.exceptions import PlotnineError, PlotnineWarning
77
from plotnine.geoms.geom import geom
8+
from plotnine.layer import layer
89
from plotnine.stats.stat import stat
910

1011

@@ -55,12 +56,12 @@ def draw(pinfo, panel_params, coord, ax, **kwargs):
5556
# not a geom manual setting
5657
g = geom_abc(weight=4)
5758
assert "weight" in g.aes_params
58-
lyr = g.to_layer()
59+
lyr = layer(geom=g)
5960
assert "weight" in lyr.stat.params
6061

6162
g = geom_abc(aes(weight="mpg"))
6263
assert "weight" in g.mapping
63-
lyr = g.to_layer()
64+
lyr = layer(geom=g)
6465
assert "weight" in lyr.stat.params
6566

6667

0 commit comments

Comments
 (0)