7272 IntoFrozenSchema ,
7373 freeze_schema ,
7474)
75+ from narwhals ._utils import check_column_names_are_unique
7576from narwhals .dtypes import DType
7677from narwhals .exceptions import ComputeError , InvalidOperationError
7778
@@ -156,7 +157,7 @@ def with_multiple_columns(self) -> ExpansionFlags:
156157def prepare_projection (
157158 exprs : Sequence [ExprIR ], / , keys : GroupByKeys = (), * , schema : IntoFrozenSchema
158159) -> tuple [Seq [NamedIR ], FrozenSchema ]:
159- """Expand IRs into named column selections .
160+ """Expand IRs into named column projections .
160161
161162 **Primary entry-point**, for `select`, `with_columns`,
162163 and any other context that requires resolving expression names.
@@ -173,13 +174,33 @@ def prepare_projection(
173174 return named_irs , frozen_schema
174175
175176
177+ def expand_selector_irs_names (
178+ selectors : Sequence [SelectorIR ],
179+ / ,
180+ keys : GroupByKeys = (),
181+ * ,
182+ schema : IntoFrozenSchema ,
183+ ) -> OutputNames :
184+ """Expand selector-only input into the column names that match.
185+
186+ Similar to `prepare_projection`, but intended for allowing a subset of `Expr` and all `Selector`s
187+ to be used in more places like `DataFrame.{drop,sort,partition_by}`.
188+
189+ Arguments:
190+ selectors: IRs that **only** contain subclasses of `SelectorIR`.
191+ keys: Names of `group_by` columns.
192+ schema: Scope to expand multi-column selectors in.
193+ """
194+ frozen_schema = freeze_schema (schema )
195+ names = tuple (_iter_expand_selector_names (selectors , keys , schema = frozen_schema ))
196+ return _ensure_valid_output_names (names , frozen_schema )
197+
198+
176199def into_named_irs (exprs : Seq [ExprIR ], names : OutputNames ) -> Seq [NamedIR ]:
177200 if len (exprs ) != len (names ):
178201 msg = f"zip length mismatch: { len (exprs )} != { len (names )} "
179202 raise ValueError (msg )
180- return tuple (
181- NamedIR (expr = remove_alias (ir ), name = name ) for ir , name in zip (exprs , names )
182- )
203+ return tuple (ir .named_ir (name , remove_alias (e )) for e , name in zip (exprs , names ))
183204
184205
185206def ensure_valid_exprs (exprs : Seq [ExprIR ], schema : FrozenSchema ) -> OutputNames :
@@ -191,13 +212,40 @@ def ensure_valid_exprs(exprs: Seq[ExprIR], schema: FrozenSchema) -> OutputNames:
191212 return output_names
192213
193214
215+ def _ensure_valid_output_names (names : Seq [str ], schema : FrozenSchema ) -> OutputNames :
216+ """Selector-only variant of `ensure_valid_exprs`."""
217+ check_column_names_are_unique (names )
218+ output_names = names
219+ if not (set (schema .names ).issuperset (output_names )):
220+ raise column_not_found_error (output_names , schema )
221+ return output_names
222+
223+
194224def _ensure_output_names_unique (exprs : Seq [ExprIR ]) -> OutputNames :
195225 names = tuple (e .meta .output_name () for e in exprs )
196226 if len (names ) != len (set (names )):
197227 raise duplicate_error (exprs )
198228 return names
199229
200230
231+ def _ensure_columns (expr : ExprIR , / ) -> Columns :
232+ if not isinstance (expr , Columns ):
233+ msg = f"Expected only column selections here, but got { expr !r} "
234+ raise NotImplementedError (msg )
235+ return expr
236+
237+
238+ def _iter_expand_selector_names (
239+ selectors : Iterable [SelectorIR ], / , keys : GroupByKeys = (), * , schema : FrozenSchema
240+ ) -> Iterator [str ]:
241+ for selector in selectors :
242+ names = _ensure_columns (replace_selector (selector , schema = schema )).names
243+ if keys :
244+ yield from (name for name in names if name not in keys )
245+ else :
246+ yield from names
247+
248+
201249# NOTE: Recursive for all `input` expressions which themselves contain `Seq[ExprIR]`
202250def rewrite_projections (
203251 input : Seq [ExprIR ], / , keys : GroupByKeys = (), * , schema : FrozenSchema
0 commit comments