Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
cd8b49d
Eager mode
FBruzzesi Jul 26, 2025
74b94c1
lazy WIP
FBruzzesi Jul 26, 2025
81f12fa
merge main
FBruzzesi Jul 26, 2025
c775ccd
fixed eager
FBruzzesi Jul 26, 2025
b3ba810
add docs, cleanse a bit
FBruzzesi Jul 26, 2025
9ee6209
fix or ignore typing issues
FBruzzesi Jul 26, 2025
a3496ed
skip if impl not installed
FBruzzesi Jul 26, 2025
dc263c8
overloads?
FBruzzesi Jul 26, 2025
f612646
fix overloads
FBruzzesi Jul 27, 2025
5a46579
merge main and add to v2
FBruzzesi Jul 28, 2025
b8b6ae2
add in v2.__all__
FBruzzesi Jul 28, 2025
22c52eb
factor out _native_int_range into utils
FBruzzesi Jul 28, 2025
8f4f647
resolve majority of typing and import issues
FBruzzesi Jul 28, 2025
7fb18bb
replace all type hints with IntegerDType, ignore import in functions
FBruzzesi Jul 28, 2025
dde4a99
Dan's suggestion
FBruzzesi Jul 28, 2025
876a771
refactor: Remove unused `IntegerType`
dangotbanned Jul 29, 2025
5ddd072
Merge remote-tracking branch 'upstream/main' into feat/int-range
dangotbanned Jul 29, 2025
5eeda2d
refactor: Reuse a single `Implementation.PYARROW._backend_version()`
dangotbanned Jul 29, 2025
704da84
fix(typing): Kinda fix `_native_int_range`
dangotbanned Jul 29, 2025
7ec01e0
refactor: Add `int64`
dangotbanned Jul 29, 2025
57393ec
fix(typing): make marco's checker happy πŸ˜‰
dangotbanned Jul 29, 2025
f1857f8
refactor(typing): Omit defaults in overloads
dangotbanned Jul 29, 2025
c94b4d4
refactor(typing): Remove unreached overload
dangotbanned Jul 29, 2025
fe2bc9d
refactor(typing): rinse/repeat for stable
dangotbanned Jul 29, 2025
fc29864
refactor: fix `polars` typing, use kwargs when required
dangotbanned Jul 30, 2025
749edfc
chore(typing): Remove unused asserts
dangotbanned Jul 30, 2025
4706a30
test: Add failing `Expr` + `eager` case
dangotbanned Jul 30, 2025
1be56cc
Merge remote-tracking branch 'upstream/main' into feat/int-range
dangotbanned Jul 30, 2025
bc0355f
low hanging feedback adjustments
FBruzzesi Jul 31, 2025
352fd4f
refactor into int_range_eager
FBruzzesi Jul 31, 2025
8b195ba
typo
FBruzzesi Jul 31, 2025
466d85d
forgot to mention about eager value in suggestion
FBruzzesi Jul 31, 2025
1575ae8
ci: Update `dtypes-import`
dangotbanned Jul 31, 2025
82e93fa
refactor: Add `PandasLikeNamespace._array_funcs`
dangotbanned Jul 31, 2025
2c52f91
refactor(suggestion): Move impl to `EagerNamespace.int_range`
dangotbanned Jul 31, 2025
f13d667
defaults for `int_range` as well
dangotbanned Jul 31, 2025
447a276
refactor: Use `_series`, pass `dtype_pa` once
dangotbanned Jul 31, 2025
1673573
Merge remote-tracking branch 'upstream/main' into feat/int-range
dangotbanned Jul 31, 2025
1098afb
revert: Undo `Int64` hack
dangotbanned Jul 31, 2025
15ae42a
refactor: Use `int_range_eager` in `with_row_index`
dangotbanned Jul 31, 2025
71e9856
always require `Expr` in `CompliantNamespace.int_range`
dangotbanned Jul 31, 2025
f3387c0
Update narwhals/functions.py
FBruzzesi Aug 1, 2025
8367cc2
Merge branch 'main' into feat/int-range
FBruzzesi Aug 1, 2025
c8af149
chore: Note remaining `np.arange` usage
dangotbanned Aug 1, 2025
ea155ca
tag as unstable
FBruzzesi Aug 1, 2025
77eb2bd
Merge branch 'main' into feat/int-range
FBruzzesi Aug 2, 2025
4c949e6
Merge branch 'main' into feat/int-range
dangotbanned Aug 4, 2025
c430d5b
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 4, 2025
3b96967
Merge branch 'main' into feat/int-range
FBruzzesi Aug 6, 2025
c98740b
Merge branch 'main' into feat/int-range
dangotbanned Aug 7, 2025
a7d4e25
Merge branch 'main' into feat/int-range
dangotbanned Aug 7, 2025
190590b
Merge remote-tracking branch 'upstream/main' into feat/int-range
dangotbanned Aug 8, 2025
cf5799a
Merge branch 'main' into feat/int-range
FBruzzesi Aug 12, 2025
f7e5c9a
Merge branch 'main' into feat/int-range
dangotbanned Aug 13, 2025
5c7e6e6
Merge remote-tracking branch 'upstream/main' into feat/int-range
dangotbanned Aug 14, 2025
5484863
Merge branch 'main' into feat/int-range
dangotbanned Aug 14, 2025
ec5ee4b
Merge branch 'main' into feat/int-range
dangotbanned Aug 15, 2025
813c101
Merge remote-tracking branch 'upstream/main' into feat/int-range
dangotbanned Aug 17, 2025
9584b0f
Merge remote-tracking branch 'upstream/main' into feat/int-range
dangotbanned Aug 19, 2025
7e81d46
Merge branch 'main' into feat/int-range
dangotbanned Aug 20, 2025
0c6495f
refactor(typing): Use `IntoBackend[EagerAllowed]`
dangotbanned Aug 20, 2025
45cf54a
docs: Remove Returns sections
dangotbanned Aug 20, 2025
abe3da7
merge main
FBruzzesi Aug 24, 2025
fb4cfda
Merge branch 'main' into feat/int-range
dangotbanned Aug 25, 2025
e8739e5
Merge branch 'main' into feat/int-range
dangotbanned Aug 27, 2025
1d8602b
Merge remote-tracking branch 'upstream/main' into feat/int-range
dangotbanned Sep 5, 2025
fb6c328
fix: Update for (#3045)
dangotbanned Sep 5, 2025
35ece71
fix: Don't treat `step` as an `Expr`
dangotbanned Sep 5, 2025
afd35f6
Merge remote-tracking branch 'upstream/main' into feat/int-range
dangotbanned Sep 13, 2025
0de173e
chore(typing): fix incompatible override
dangotbanned Sep 13, 2025
80c422a
Merge remote-tracking branch 'upstream/main' into feat/int-range
dangotbanned Oct 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/api-reference/narwhals.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Here are the top-level functions available in Narwhals.
- generate_temporary_column_name
- get_level
- get_native_namespace
- int_range
- is_ordered_categorical
- len
- lit
Expand Down
2 changes: 2 additions & 0 deletions narwhals/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
from_dict,
from_numpy,
get_level,
int_range,
len_ as len,
lit,
max,
Expand Down Expand Up @@ -143,6 +144,7 @@
"generate_temporary_column_name",
"get_level",
"get_native_namespace",
"int_range",
"is_ordered_categorical",
"len",
"lit",
Expand Down
11 changes: 4 additions & 7 deletions narwhals/_arrow/dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -472,16 +472,13 @@ def to_dict(
def with_row_index(self, name: str, order_by: Sequence[str] | None) -> Self:
plx = self.__narwhals_namespace__()
if order_by is None:
import numpy as np # ignore-banned-import

data = pa.array(np.arange(len(self), dtype=np.int64))
row_index = plx._expr._from_series(
plx._series.from_iterable(data, context=self, name=name)
row_index = plx.int_range(
start=0, end=len(self), step=1, dtype=self._version.dtypes.Int64()
)
else:
rank = plx.col(order_by[0]).rank("ordinal", descending=False)
row_index = (rank.over(partition_by=[], order_by=order_by) - 1).alias(name)
return self.select(row_index, plx.all())
row_index = rank.over(partition_by=[], order_by=order_by) - 1
return self.select(row_index.alias(name), plx.all())

def filter(self, predicate: ArrowExpr | list[bool | None]) -> Self:
if isinstance(predicate, list):
Expand Down
44 changes: 44 additions & 0 deletions narwhals/_arrow/namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from narwhals._arrow.typing import ArrayOrScalar, ChunkedArrayAny, Incomplete
from narwhals._compliant.typing import ScalarKwargs
from narwhals._utils import Version
from narwhals.dtypes import IntegerType
from narwhals.typing import IntoDType, NonNestedLiteral


Expand Down Expand Up @@ -284,6 +285,49 @@ def func(df: ArrowDataFrame) -> list[ArrowSeries]:
context=self,
)

def int_range(
self,
start: int | ArrowExpr,
end: int | ArrowExpr,
step: int,
*,
dtype: IntegerType | type[IntegerType],
) -> ArrowExpr:
def func(df: ArrowDataFrame) -> list[ArrowSeries]:
if isinstance(start, ArrowExpr):
start_eval = start(df)[0]
name = start_eval.name
start_value = start_eval.item()
else:
name = "literal"
start_value = start

end_value = end(df)[0].item() if isinstance(end, ArrowExpr) else end
return [
ArrowSeries._int_range(
start=start_value,
end=end_value,
step=step,
dtype=dtype,
context=self,
name=name,
)
]

evaluate_output_names = (
combine_evaluate_output_names(start)
if isinstance(start, ArrowExpr)
else lambda _df: ["literal"]
)
return self._expr._from_callable(
func=func,
depth=0,
function_name="int_range",
evaluate_output_names=evaluate_output_names,
alias_output_names=None,
context=self,
)


class ArrowWhen(EagerWhen[ArrowDataFrame, ArrowSeries, ArrowExpr, "ChunkedArrayAny"]):
@property
Expand Down
27 changes: 26 additions & 1 deletion narwhals/_arrow/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
)
from narwhals._compliant.series import HistData
from narwhals._utils import Version, _LimitedContext
from narwhals.dtypes import DType
from narwhals.dtypes import DType, IntegerType
from narwhals.typing import (
ClosedInterval,
FillNullStrategy,
Expand Down Expand Up @@ -161,6 +161,31 @@ def from_iterable(
chunked_array([data], dtype_pa), name=name, context=context
)

@classmethod
def _int_range(
cls,
start: int,
end: int,
step: int,
dtype: IntegerType | type[IntegerType],
context: _LimitedContext,
name: str,
) -> Self:
version = context._version
dtype_pa = narwhals_to_native_dtype(dtype, version)
if cls._implementation._backend_version() < (21, 0, 0): # pragma: no cover
import numpy as np # ignore-banned-import

data = np.arange(start=start, stop=end, step=step)
else:
data = pc.cast( # type: ignore[assignment]
pa.arange(start=start, stop=end, step=step), # type: ignore[attr-defined]
dtype_pa,
)
return cls.from_native(
chunked_array([data], dtype_pa), name=name, context=context
)

def _from_scalar(self, value: Any) -> Self:
if hasattr(value, "as_py"):
value = value.as_py()
Expand Down
13 changes: 12 additions & 1 deletion narwhals/_compliant/namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from narwhals._utils import (
exclude_column_names,
get_column_names,
not_implemented,
passthrough_column_names,
)
from narwhals.dependencies import is_numpy_array_2d
Expand All @@ -31,7 +32,7 @@
from narwhals._compliant.selectors import CompliantSelectorNamespace
from narwhals._compliant.when_then import CompliantWhen, EagerWhen
from narwhals._utils import Implementation, Version
from narwhals.dtypes import DType
from narwhals.dtypes import DType, IntegerType
from narwhals.schema import Schema
from narwhals.typing import (
ConcatMethod,
Expand Down Expand Up @@ -92,6 +93,14 @@ def when(
def concat_str(
self, *exprs: CompliantExprT, separator: str, ignore_nulls: bool
) -> CompliantExprT: ...
def int_range(
self,
start: int | CompliantExprT,
end: int | CompliantExprT,
step: int,
*,
dtype: IntegerType | type[IntegerType],
) -> CompliantExprT: ...
@property
def selectors(self) -> CompliantSelectorNamespace[Any, Any]: ...
@property
Expand Down Expand Up @@ -139,6 +148,8 @@ def from_native(self, data: NativeFrameT_co | Any, /) -> CompliantLazyFrameT:
msg = f"Unsupported type: {type(data).__name__!r}"
raise TypeError(msg)

int_range: not_implemented = not_implemented()


class EagerNamespace(
DepthTrackingNamespace[EagerDataFrameT, EagerExprT],
Expand Down
12 changes: 11 additions & 1 deletion narwhals/_compliant/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
from narwhals._compliant.expr import CompliantExpr, EagerExpr
from narwhals._compliant.namespace import CompliantNamespace, EagerNamespace
from narwhals._utils import Implementation, Version, _LimitedContext
from narwhals.dtypes import DType
from narwhals.dtypes import DType, IntegerType
from narwhals.series import Series
from narwhals.typing import (
ClosedInterval,
Expand Down Expand Up @@ -122,6 +122,16 @@ def from_iterable(
name: str = "",
dtype: IntoDType | None = None,
) -> Self: ...
@classmethod
def _int_range(
cls,
start: int,
end: int,
step: int,
dtype: IntegerType | type[IntegerType],
context: _LimitedContext,
name: str,
) -> Self: ...
def to_narwhals(self) -> Series[NativeSeriesT]:
return self._version.series(self, level="full")

Expand Down
3 changes: 1 addition & 2 deletions narwhals/_pandas_like/dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,8 +413,7 @@ def estimated_size(self, unit: SizeUnit) -> int | float:
def with_row_index(self, name: str, order_by: Sequence[str] | None) -> Self:
plx = self.__narwhals_namespace__()
if order_by is None:
size = len(self)
data = self._array_funcs.arange(size)
data = self._array_funcs.arange(len(self))

row_index = plx._expr._from_series(
plx._series.from_iterable(
Expand Down
43 changes: 43 additions & 0 deletions narwhals/_pandas_like/namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

from narwhals._compliant.typing import ScalarKwargs
from narwhals._utils import Implementation, Version
from narwhals.dtypes import IntegerType
from narwhals.typing import IntoDType, NonNestedLiteral


Expand Down Expand Up @@ -370,6 +371,48 @@ def func(df: PandasLikeDataFrame) -> list[PandasLikeSeries]:
context=self,
)

def int_range(
self,
start: int | PandasLikeExpr,
end: int | PandasLikeExpr,
step: int,
*,
dtype: IntegerType | type[IntegerType],
) -> PandasLikeExpr:
def func(df: PandasLikeDataFrame) -> list[PandasLikeSeries]:
if isinstance(start, PandasLikeExpr):
start_eval = start(df)[0]
name = start_eval.name
start_value = start_eval.item()
else: # pragma: no cover
name = "literal"
start_value = start
end_value = end(df)[0].item() if isinstance(end, PandasLikeExpr) else end
return [
PandasLikeSeries._int_range(
start=start_value,
end=end_value,
step=step,
dtype=dtype,
context=self,
name=name,
)
]

evaluate_output_names = (
combine_evaluate_output_names(start)
if isinstance(start, PandasLikeExpr)
else lambda _df: ["literal"]
)
return self._expr._from_callable(
func=func,
depth=0,
function_name="int_range",
evaluate_output_names=evaluate_output_names,
alias_output_names=None,
context=self,
)


class _NativeConcat(Protocol[NativeDataFrameT, NativeSeriesT]):
@overload
Expand Down
18 changes: 16 additions & 2 deletions narwhals/_pandas_like/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
from narwhals._pandas_like.dataframe import PandasLikeDataFrame
from narwhals._pandas_like.namespace import PandasLikeNamespace
from narwhals._utils import Version, _LimitedContext
from narwhals.dtypes import DType
from narwhals.dtypes import DType, IntegerType
from narwhals.typing import (
ClosedInterval,
FillNullStrategy,
Expand Down Expand Up @@ -182,6 +182,20 @@ def from_iterable(
kwds["index"] = index
return cls.from_native(ns.Series(data, name=name, **kwds), context=context)

@classmethod
def _int_range(
cls,
start: int,
end: int,
step: int,
dtype: IntegerType | type[IntegerType],
context: _LimitedContext,
name: str,
) -> Self:
array_funcs = import_array_module(context._implementation)
data = array_funcs.arange(start, end, step)
return cls.from_iterable(data, context=context, name=name, dtype=dtype)

@staticmethod
def _is_native(obj: Any) -> TypeIs[Any]:
return is_pandas_like_series(obj) # pragma: no cover
Expand Down Expand Up @@ -311,7 +325,7 @@ def cast(self, dtype: IntoDType) -> Self:
)
return self._with_native(self.native.astype(pd_dtype), preserve_broadcast=True)

def item(self, index: int | None) -> Any:
def item(self, index: int | None = None) -> Any:
# cuDF doesn't have Series.item().
if index is None:
if len(self) != 1:
Expand Down
21 changes: 21 additions & 0 deletions narwhals/_polars/namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@
from collections.abc import Iterable, Mapping, Sequence
from datetime import timezone

from polars.datatypes import IntegerType as PlIntegerType

from narwhals._compliant import CompliantSelectorNamespace, CompliantWhen
from narwhals._polars.dataframe import Method, PolarsDataFrame, PolarsLazyFrame
from narwhals._polars.typing import FrameT
from narwhals._utils import Version, _LimitedContext
from narwhals.dtypes import IntegerType
from narwhals.schema import Schema
from narwhals.typing import Into1DArray, IntoDType, TimeUnit, _2DArray

Expand Down Expand Up @@ -201,6 +204,24 @@ def concat_str(
version=self._version,
)

def int_range(
self,
start: int | PolarsExpr,
end: int | PolarsExpr,
step: int,
*,
dtype: IntegerType | type[IntegerType],
) -> PolarsExpr:
start_ = start if isinstance(start, int) else start.native
end_ = end if isinstance(end, int) else end.native
pl_dtype: PlIntegerType = narwhals_to_native_dtype(
dtype=dtype, version=self._version
) # type: ignore[assignment]
return self._expr(
pl.int_range(start=start_, end=end_, step=step, dtype=pl_dtype, eager=False),
version=self._version,
)

# NOTE: Implementation is too different to annotate correctly (vs other `*SelectorNamespace`)
# 1. Others have lots of private stuff for code reuse
# i. None of that is useful here
Expand Down
20 changes: 19 additions & 1 deletion narwhals/_polars/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@

import pandas as pd
import pyarrow as pa
from polars.datatypes import IntegerType as PlIntegerType
from typing_extensions import Self, TypeAlias, TypeIs

from narwhals._polars.dataframe import Method, PolarsDataFrame
from narwhals._polars.expr import PolarsExpr
from narwhals._polars.namespace import PolarsNamespace
from narwhals._utils import Version, _LimitedContext
from narwhals.dtypes import DType
from narwhals.dtypes import DType, IntegerType
from narwhals.series import Series
from narwhals.typing import Into1DArray, IntoDType, MultiIndexSelector, _1DArray

Expand Down Expand Up @@ -164,6 +165,23 @@ def from_iterable(
native = pl.Series(name=name, values=cast("Sequence[Any]", data), dtype=dtype_pl)
return cls.from_native(native, context=context)

@classmethod
def _int_range(
cls,
start: int,
end: int,
step: int,
dtype: IntegerType | type[IntegerType],
context: _LimitedContext,
name: str,
) -> Self:
version = context._version
dtype_pl: PlIntegerType = narwhals_to_native_dtype(dtype, version) # type: ignore[assignment]
return cls.from_native(
pl.int_range(start=start, end=end, step=step, dtype=dtype_pl, eager=True),
context=context,
).alias(name)

@staticmethod
def _is_native(obj: pl.Series | Any) -> TypeIs[pl.Series]:
return isinstance(obj, pl.Series)
Expand Down
Loading
Loading