@@ -96,6 +96,30 @@ def matches_column(self, name: str, dtype: DType) -> bool:
9696 return dtype in self .dtypes
9797
9898
99+ class ByName (Selector ):
100+ # NOTE: `polars` allows this and `by_index` to redefine schema order in a `select`
101+ # > Matching columns are returned in the order in which they are declared in
102+ # > the selector, not the underlying schema order.
103+ # If you wanna support that (later), then a `frozenset` won't work
104+ __slots__ = ("names" ,)
105+ names : frozenset [str ]
106+
107+ def __repr__ (self ) -> str :
108+ els = ", " .join (f"{ nm !r} " for nm in sorted (self .names ))
109+ return f"ncs.by_name({ els } )"
110+
111+ @staticmethod
112+ def from_names (* names : OneOrIterable [str ]) -> ByName :
113+ return ByName (names = frozenset (flatten_hash_safe (names )))
114+
115+ @staticmethod
116+ def from_name (name : str , / ) -> ByName :
117+ return ByName (names = frozenset ((name ,)))
118+
119+ def matches_column (self , name : str , dtype : DType ) -> bool :
120+ return name in self .names
121+
122+
99123class Categorical (Selector ):
100124 def __repr__ (self ) -> str :
101125 return "ncs.categorical()"
@@ -192,12 +216,6 @@ class Matches(Selector):
192216 def from_string (pattern : str , / ) -> Matches :
193217 return Matches (pattern = re .compile (pattern ))
194218
195- @staticmethod
196- def from_names (* names : OneOrIterable [str ]) -> Matches :
197- """Implements `cs.by_name` to support `__r<op>__` with column selections."""
198- it = flatten_hash_safe (names )
199- return Matches .from_string (f"^({ '|' .join (re .escape (name ) for name in it )} )$" )
200-
201219 def __repr__ (self ) -> str :
202220 return f"ncs.matches(pattern={ self .pattern .pattern !r} )"
203221
@@ -245,7 +263,11 @@ def by_dtype(*dtypes: OneOrIterable[DType | type[DType]]) -> expr.Selector:
245263
246264
247265def by_name (* names : OneOrIterable [str ]) -> expr .Selector :
248- return Matches .from_names (* names ).to_selector ().to_narwhals ()
266+ if len (names ) == 1 and isinstance (names [0 ], str ):
267+ sel = ByName .from_name (names [0 ])
268+ else :
269+ sel = ByName .from_names (* names )
270+ return sel .to_selector ().to_narwhals ()
249271
250272
251273def boolean () -> expr .Selector :
0 commit comments