Skip to content
9 changes: 8 additions & 1 deletion narwhals/_compliant/namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def concat(
self,
items: Iterable[CompliantFrameT],
*,
how: Literal["horizontal", "vertical", "diagonal"],
how: Literal["vertical", "diagonal"],
) -> CompliantFrameT: ...
def when(
self, predicate: CompliantExprT
Expand Down Expand Up @@ -184,3 +184,10 @@ def from_numpy(
if is_numpy_array_2d(data):
return self._dataframe.from_numpy(data, schema=schema, context=self)
return self._series.from_numpy(data, context=self)

def concat(
self,
items: Iterable[EagerDataFrameT],
*,
how: Literal["horizontal", "vertical", "diagonal"],
) -> EagerDataFrameT: ...
20 changes: 1 addition & 19 deletions narwhals/_dask/namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ def concat(
self: Self,
items: Iterable[DaskLazyFrame],
*,
how: Literal["horizontal", "vertical", "diagonal"],
how: Literal["vertical", "diagonal"],
) -> DaskLazyFrame:
if not items:
msg = "No items to concatenate" # pragma: no cover
Expand All @@ -178,24 +178,6 @@ def concat(
backend_version=self._backend_version,
version=self._version,
)
if how == "horizontal":
all_column_names: list[str] = [
column for frame in dfs for column in frame.columns
]
if len(all_column_names) != len(set(all_column_names)): # pragma: no cover
duplicates = [
i for i in all_column_names if all_column_names.count(i) > 1
]
msg = (
f"Columns with name(s): {', '.join(duplicates)} "
"have more than one occurrence"
)
raise AssertionError(msg)
return DaskLazyFrame(
dd.concat(dfs, axis=1, join="outer"),
backend_version=self._backend_version,
version=self._version,
)
Comment on lines -178 to -195
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beautiful 😍

if how == "diagonal":
return DaskLazyFrame(
dd.concat(dfs, axis=0, join="outer"),
Expand Down
5 changes: 1 addition & 4 deletions narwhals/_duckdb/namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,8 @@ def concat(
self: Self,
items: Iterable[DuckDBLazyFrame],
*,
how: Literal["horizontal", "vertical", "diagonal"],
how: Literal["vertical", "diagonal"],
) -> DuckDBLazyFrame:
if how == "horizontal":
msg = "horizontal concat not supported for duckdb. Please join instead"
raise TypeError(msg)
if how == "diagonal":
msg = "Not implemented yet"
raise NotImplementedError(msg)
Expand Down
9 changes: 1 addition & 8 deletions narwhals/_spark_like/namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,16 +192,9 @@ def concat(
self: Self,
items: Iterable[SparkLikeLazyFrame],
*,
how: Literal["horizontal", "vertical", "diagonal"],
how: Literal["vertical", "diagonal"],
) -> SparkLikeLazyFrame:
dfs = [item._native_frame for item in items]
if how == "horizontal":
msg = (
"Horizontal concatenation is not supported for LazyFrame backed by "
"a PySpark DataFrame."
)
raise NotImplementedError(msg)

if how == "vertical":
cols_0 = dfs[0].columns
for i, df in enumerate(dfs[1:], start=1):
Expand Down
23 changes: 17 additions & 6 deletions narwhals/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from narwhals.dependencies import is_numpy_array
from narwhals.dependencies import is_numpy_array_2d
from narwhals.dependencies import is_pyarrow_table
from narwhals.exceptions import InvalidOperationError
from narwhals.expr import Expr
from narwhals.series import Series
from narwhals.translate import from_native
Expand Down Expand Up @@ -105,12 +106,13 @@ def concat(

- vertical: Concatenate vertically. Column names must match.
- horizontal: Concatenate horizontally. If lengths don't match, then
missing rows are filled with null values.
missing rows are filled with null values. This is only supported
when all inputs are (eager) DataFrames.
- diagonal: Finds a union between the column schemas and fills missing column
values with null.

Returns:
A new DataFrame, Lazyframe resulting from the concatenation.
A new DataFrame or Lazyframe resulting from the concatenation.

Raises:
TypeError: The items to concatenate should either all be eager, or all lazy
Expand Down Expand Up @@ -177,16 +179,25 @@ def concat(
|z: [[null,null],["x","y"]]|
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
"""
if how not in {"horizontal", "vertical", "diagonal"}: # pragma: no cover
msg = "Only vertical, horizontal and diagonal concatenations are supported."
raise NotImplementedError(msg)
from narwhals.dataframe import LazyFrame

if not items:
msg = "No items to concatenate"
msg = "No items to concatenate."
raise ValueError(msg)
items = list(items)
validate_laziness(items)
if how not in {"horizontal", "vertical", "diagonal"}: # pragma: no cover
msg = "Only vertical, horizontal and diagonal concatenations are supported."
raise NotImplementedError(msg)
first_item = items[0]
plx = first_item.__narwhals_namespace__()
if isinstance(first_item, LazyFrame) and how == "horizontal":
msg = (
"Horizontal concatenation is not supported for LazyFrames.\n\n"
"Hint: you may want to use `join` instead."
)
raise InvalidOperationError(msg)

return first_item._with_compliant(
plx.concat([df._compliant_frame for df in items], how=how),
)
Expand Down
7 changes: 4 additions & 3 deletions narwhals/stable/v1/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2061,7 +2061,7 @@ def concat(
def concat(
items: Iterable[LazyFrame[IntoFrameT]],
*,
how: Literal["horizontal", "vertical", "diagonal"] = "vertical",
how: Literal["vertical", "diagonal"] = "vertical",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean to change the signature only in v1?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope, thanks πŸ˜„ will fix later

) -> LazyFrame[IntoFrameT]: ...


Expand All @@ -2086,12 +2086,13 @@ def concat(

- vertical: Concatenate vertically. Column names must match.
- horizontal: Concatenate horizontally. If lengths don't match, then
missing rows are filled with null values.
missing rows are filled with null values. This is only supported
when all inputs are (eager) DataFrames.
- diagonal: Finds a union between the column schemas and fills missing column
values with null.

Returns:
A new DataFrame, Lazyframe resulting from the concatenation.
A new DataFrame or Lazyframe resulting from the concatenation.

Raises:
TypeError: The items to concatenate should either all be eager, or all lazy
Expand Down
14 changes: 7 additions & 7 deletions tests/frame/concat_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,18 @@
import pytest

import narwhals.stable.v1 as nw
from narwhals.exceptions import InvalidOperationError
from tests.utils import Constructor
from tests.utils import ConstructorEager
from tests.utils import assert_equal_data


def test_concat_horizontal(
constructor: Constructor, request: pytest.FixtureRequest
) -> None:
if ("pyspark" in str(constructor)) or "duckdb" in str(constructor):
request.applymarker(pytest.mark.xfail)
def test_concat_horizontal(constructor_eager: ConstructorEager) -> None:
data = {"a": [1, 3, 2], "b": [4, 4, 6], "z": [7.0, 8.0, 9.0]}
df_left = nw.from_native(constructor(data)).lazy()
df_left = nw.from_native(constructor_eager(data), eager_only=True)

data_right = {"c": [6, 12, -1], "d": [0, -4, 2]}
df_right = nw.from_native(constructor(data_right)).lazy()
df_right = nw.from_native(constructor_eager(data_right), eager_only=True)

result = nw.concat([df_left, df_right], how="horizontal")
expected = {
Expand All @@ -30,6 +28,8 @@ def test_concat_horizontal(

with pytest.raises(ValueError, match="No items"):
nw.concat([])
with pytest.raises(InvalidOperationError):
nw.concat([df_left.lazy()], how="horizontal")


def test_concat_vertical(constructor: Constructor) -> None:
Expand Down
Loading