Skip to content

Commit 77a0150

Browse files
authored
feat: Adds DataFrame.iter_columns (#2104)
1 parent 5383756 commit 77a0150

File tree

6 files changed

+71
-0
lines changed

6 files changed

+71
-0
lines changed

docs/api-reference/dataframe.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
- is_empty
2424
- is_unique
2525
- item
26+
- iter_columns
2627
- iter_rows
2728
- join
2829
- join_asof

narwhals/_arrow/dataframe.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,17 @@ def rows(self: Self, *, named: bool) -> list[tuple[Any, ...]] | list[dict[str, A
153153
return list(self.iter_rows(named=False, buffer_size=512)) # type: ignore[return-value]
154154
return self._native_frame.to_pylist()
155155

156+
def iter_columns(self) -> Iterator[ArrowSeries]:
157+
from narwhals._arrow.series import ArrowSeries
158+
159+
for name, series in zip(self.columns, self._native_frame.itercolumns()):
160+
yield ArrowSeries(
161+
series,
162+
name=name,
163+
backend_version=self._backend_version,
164+
version=self._version,
165+
)
166+
156167
def iter_rows(
157168
self: Self, *, named: bool, buffer_size: int
158169
) -> Iterator[tuple[Any, ...]] | Iterator[dict[str, Any]]:

narwhals/_pandas_like/dataframe.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,15 @@ def rows(self: Self, *, named: bool) -> list[tuple[Any, ...]] | list[dict[str, A
341341

342342
return self._native_frame.to_dict(orient="records")
343343

344+
def iter_columns(self) -> Iterator[PandasLikeSeries]:
345+
for _name, series in self._native_frame.items(): # noqa: PERF102
346+
yield PandasLikeSeries(
347+
series,
348+
implementation=self._implementation,
349+
backend_version=self._backend_version,
350+
version=self._version,
351+
)
352+
344353
def iter_rows(
345354
self: Self,
346355
*,

narwhals/_polars/dataframe.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from typing import TYPE_CHECKING
44
from typing import Any
5+
from typing import Iterator
56
from typing import Literal
67
from typing import Sequence
78
from typing import overload
@@ -232,6 +233,14 @@ def get_column(self: Self, name: str) -> PolarsSeries:
232233
version=self._version,
233234
)
234235

236+
def iter_columns(self) -> Iterator[PolarsSeries]:
237+
from narwhals._polars.series import PolarsSeries
238+
239+
for series in self._native_frame.iter_columns():
240+
yield PolarsSeries(
241+
series, backend_version=self._backend_version, version=self._version
242+
)
243+
235244
@property
236245
def columns(self: Self) -> list[str]:
237246
return self._native_frame.columns

narwhals/dataframe.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1235,6 +1235,37 @@ def rows(
12351235
"""
12361236
return self._compliant_frame.rows(named=named) # type: ignore[no-any-return]
12371237

1238+
def iter_columns(self: Self) -> Iterator[Series[Any]]:
1239+
"""Returns an iterator over the columns of this DataFrame.
1240+
1241+
Yields:
1242+
A Narwhals Series, backed by a native series.
1243+
1244+
Examples:
1245+
>>> import pandas as pd
1246+
>>> import narwhals as nw
1247+
>>> df_native = pd.DataFrame({"foo": [1, 2], "bar": [6.0, 7.0]})
1248+
>>> iter_columns = nw.from_native(df_native).iter_columns()
1249+
>>> next(iter_columns)
1250+
┌───────────────────────┐
1251+
| Narwhals Series |
1252+
|-----------------------|
1253+
|0 1 |
1254+
|1 2 |
1255+
|Name: foo, dtype: int64|
1256+
└───────────────────────┘
1257+
>>> next(iter_columns)
1258+
┌─────────────────────────┐
1259+
| Narwhals Series |
1260+
|-------------------------|
1261+
|0 6.0 |
1262+
|1 7.0 |
1263+
|Name: bar, dtype: float64|
1264+
└─────────────────────────┘
1265+
"""
1266+
for series in self._compliant_frame.iter_columns():
1267+
yield self._series(series, level=self._level)
1268+
12381269
@overload
12391270
def iter_rows(
12401271
self: Self, *, named: Literal[False], buffer_size: int = ...

tests/frame/columns_test.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88

99
if TYPE_CHECKING:
1010
from tests.utils import Constructor
11+
from tests.utils import ConstructorEager
12+
13+
data = {"a": [1, 3, 2], "b": [4, 4, 6], "z": [7.0, 8.0, 9.0]}
1114

1215

1316
@pytest.mark.filterwarnings("ignore:Determining|Resolving.*")
@@ -17,3 +20,10 @@ def test_columns(constructor: Constructor) -> None:
1720
result = df.columns
1821
expected = ["a", "b", "z"]
1922
assert result == expected
23+
24+
25+
def test_iter_columns(constructor_eager: ConstructorEager) -> None:
26+
df = nw.from_native(constructor_eager(data), eager_only=True)
27+
expected = df.to_dict(as_series=True)
28+
result = {series.name: series for series in df.iter_columns()}
29+
assert result == expected

0 commit comments

Comments
 (0)