Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 crates/polars-expr/src/dispatch/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,7 @@ pub fn function_expr_to_udf(func: IRFunctionExpr) -> SpecialEq<Arc<dyn ColumnsUd
#[cfg(feature = "fused")]
F::Fused(op) => map_as_slice!(misc::fused, op),
F::ConcatExpr { rechunk } => map_as_slice!(misc::concat_expr, rechunk),
F::ConcatList => wrap!(list::concat),
#[cfg(feature = "cov")]
F::Correlation { method } => map_as_slice!(misc::corr, method),
#[cfg(feature = "peaks")]
Expand Down
2 changes: 1 addition & 1 deletion crates/polars-plan/dsl-schema-hashes.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
"FileSinkOptions": "edebcf5e3965add5e4fd1be14ca6bdddc55fa22e6e829dca04beb321de0c992c",
"FileWriteFormat": "1a685aba7dd5d6c0aefc99a9060d1b57f166ea44ef57ad0d0d0c565dbabda811",
"FillNullStrategy": "459a9a9702415f9ca9e5218bb573609a60291e73162c38fbc046c97feb1b7500",
"FunctionExpr": "1b723aff4de79571f7263b470de2d4bfb9f1204ae91c7e5e09b37f66bc27875c",
"FunctionExpr": "04d59b9375612be6a620c3d3b8e9f7eeb4c4f8896e098e3abedb9336ff8694bf",
"FunctionFlags": "54fd84a1b628c426b8d0f5e9bca174093e07da8992a9a9bb4c191d07133e0046",
"FunctionOptions": "0784524479a30a7d91b890b03feac9eca6c46d04f0a7c3f4a9a2d827c3e34b5e",
"GroupbyOptions": "0cda61fc19eb9866157ae4afeed3dc018294aaea5f02692b085885de771bfcdb",
Expand Down
3 changes: 3 additions & 0 deletions crates/polars-plan/src/dsl/function_expr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ pub enum FunctionExpr {
UpperBound,
LowerBound,
ConcatExpr(bool),
ConcatList,
#[cfg(feature = "cov")]
Correlation {
method: correlation::CorrelationMethod,
Expand Down Expand Up @@ -596,6 +597,7 @@ impl Hash for FunctionExpr {
UpperBound => {},
LowerBound => {},
ConcatExpr(rechunk) => rechunk.hash(state),
ConcatList => {},
#[cfg(feature = "peaks")]
PeakMin => {},
#[cfg(feature = "peaks")]
Expand Down Expand Up @@ -834,6 +836,7 @@ impl Display for FunctionExpr {
UpperBound => "upper_bound",
LowerBound => "lower_bound",
ConcatExpr(..) => "concat_expr",
ConcatList => "concat_list",
#[cfg(feature = "cov")]
Correlation { method, .. } => return Display::fmt(method, f),
#[cfg(feature = "peaks")]
Expand Down
2 changes: 1 addition & 1 deletion crates/polars-plan/src/dsl/functions/concat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ pub fn concat_list<E: AsRef<[IE]>, IE: Into<Expr> + Clone>(s: E) -> PolarsResult

Ok(Expr::Function {
input: s,
function: FunctionExpr::ListExpr(ListFunction::Concat),
function: FunctionExpr::ConcatList,
})
}

Expand Down
1 change: 0 additions & 1 deletion crates/polars-plan/src/dsl/functions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ pub use temporal::*;

#[cfg(feature = "arg_where")]
use crate::dsl::function_expr::FunctionExpr;
use crate::dsl::function_expr::ListFunction;
#[cfg(all(feature = "concat_str", feature = "strings"))]
use crate::dsl::function_expr::StringFunction;
use crate::dsl::*;
Expand Down
11 changes: 11 additions & 0 deletions crates/polars-plan/src/dsl/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,17 @@ impl ListNameSpace {
}
}

/// Concatenate the list with another list.
pub fn concat<E: AsRef<[IE]>, IE: Into<Expr> + Clone>(self, other: E) -> Expr {
let mut input: Vec<_> = other.as_ref().iter().map(|e| e.clone().into()).collect();
input.insert(0, self.0);

Expr::Function {
input,
function: FunctionExpr::ListExpr(ListFunction::Concat),
}
}

pub fn agg<E: Into<Expr>>(self, other: E) -> Expr {
Expr::Eval {
expr: Arc::new(self.0),
Expand Down
2 changes: 1 addition & 1 deletion crates/polars-plan/src/plans/aexpr/function_expr/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ impl IRListFunction {
pub(super) fn get_field(&self, mapper: FieldsMapper) -> PolarsResult<Field> {
use IRListFunction::*;
match self {
Concat => mapper.map_to_list_supertype(),
Concat => mapper.ensure_is_list()?.map_to_list_supertype(),
#[cfg(feature = "is_in")]
Contains { nulls_equal: _ } => mapper.ensure_is_list()?.with_dtype(DataType::Boolean),
#[cfg(feature = "list_drop_nulls")]
Expand Down
5 changes: 5 additions & 0 deletions crates/polars-plan/src/plans/aexpr/function_expr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ pub enum IRFunctionExpr {
ConcatExpr {
rechunk: bool,
},
ConcatList,
#[cfg(feature = "cov")]
Correlation {
method: correlation::IRCorrelationMethod,
Expand Down Expand Up @@ -613,6 +614,7 @@ impl Hash for IRFunctionExpr {
#[cfg(feature = "round_series")]
Ceil => {},
ConcatExpr { rechunk } => rechunk.hash(state),
ConcatList => {},
#[cfg(feature = "peaks")]
PeakMin => {},
#[cfg(feature = "peaks")]
Expand Down Expand Up @@ -853,6 +855,7 @@ impl Display for IRFunctionExpr {
#[cfg(feature = "fused")]
Fused(fused) => return Display::fmt(fused, f),
ConcatExpr { .. } => "concat_expr",
ConcatList => "concat_list",
#[cfg(feature = "cov")]
Correlation { method, .. } => return Display::fmt(method, f),
#[cfg(feature = "peaks")]
Expand Down Expand Up @@ -1172,6 +1175,8 @@ impl IRFunctionExpr {
F::ConcatExpr { .. } => FunctionOptions::groupwise()
.with_flags(|f| f | FunctionFlags::INPUT_WILDCARD_EXPANSION)
.with_supertyping(Default::default()),
F::ConcatList => FunctionOptions::elementwise()
.with_flags(|f| f | FunctionFlags::INPUT_WILDCARD_EXPANSION),
#[cfg(feature = "cov")]
F::Correlation { .. } => {
FunctionOptions::aggregation().with_supertyping(Default::default())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ impl IRFunctionExpr {
#[cfg(feature = "fused")]
Fused(_) => mapper.map_to_supertype(),
ConcatExpr { .. } => mapper.map_to_supertype(),
ConcatList => mapper.map_to_list_supertype(),
#[cfg(feature = "cov")]
Correlation { .. } => mapper.map_to_float_dtype(),
#[cfg(feature = "peaks")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ fn function_input_wildcard_expansion(function: &FunctionExpr) -> FunctionExpansi
| F::Coalesce
| F::ListExpr(ListFunction::Concat)
| F::ConcatExpr(..)
| F::ConcatList
| F::MinHorizontal
| F::MaxHorizontal
| F::FoldHorizontal { .. }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -914,6 +914,7 @@ pub(super) fn convert_functions(
));
},
F::ConcatExpr(rechunk) => I::ConcatExpr { rechunk },
F::ConcatList => I::ConcatList,
#[cfg(feature = "cov")]
F::Correlation { method } => {
use CorrelationMethod as C;
Expand Down
1 change: 1 addition & 0 deletions crates/polars-plan/src/plans/conversion/ir_to_dsl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1024,6 +1024,7 @@ pub fn ir_function_to_dsl(input: Vec<Expr>, function: IRFunctionExpr) -> Expr {
};
},
IF::ConcatExpr { rechunk } => F::ConcatExpr(rechunk),
IF::ConcatList => F::ConcatList,
#[cfg(feature = "cov")]
IF::Correlation { method } => {
use CorrelationMethod as C;
Expand Down
5 changes: 5 additions & 0 deletions crates/polars-python/src/expr/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ impl PyExpr {
self.inner.clone().list().eval(expr.inner).into()
}

fn list_concat(&self, other: Vec<PyExpr>) -> Self {
let other: Vec<_> = other.into_iter().map(|e| e.inner).collect();
self.inner.clone().list().concat(&other).into()
}

fn list_agg(&self, expr: PyExpr) -> Self {
self.inner.clone().list().agg(expr.inner).into()
}
Expand Down
3 changes: 3 additions & 0 deletions crates/polars-python/src/lazyframe/visitor/expr_nodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1352,6 +1352,9 @@ pub(crate) fn into_py(py: Python<'_>, expr: &AExpr) -> PyResult<Py<PyAny>> {
IRFunctionExpr::ConcatExpr { .. } => {
return Err(PyNotImplementedError::new_err("concat expr"));
},
IRFunctionExpr::ConcatList => {
return Err(PyNotImplementedError::new_err("concat list"));
},
IRFunctionExpr::Correlation { .. } => {
return Err(PyNotImplementedError::new_err("corr"));
},
Expand Down
1 change: 1 addition & 0 deletions py-polars/src/polars/_plr.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -1592,6 +1592,7 @@ class PyExpr:
def list_count_matches(self, expr: PyExpr) -> PyExpr: ...
def list_diff(self, n: int, null_behavior: NullBehavior) -> PyExpr: ...
def list_eval(self, expr: PyExpr, _parallel: bool) -> PyExpr: ...
def list_concat(self, other: list[PyExpr]) -> PyExpr: ...
def list_agg(self, expr: PyExpr) -> PyExpr: ...
def list_filter(self, predicate: PyExpr) -> PyExpr: ...
def list_get(self, index: PyExpr, null_on_oob: bool) -> PyExpr: ...
Expand Down
4 changes: 2 additions & 2 deletions py-polars/src/polars/expr/list.py
Original file line number Diff line number Diff line change
Expand Up @@ -530,8 +530,8 @@ def concat(self, other: list[Expr | str] | Expr | str | Series | list[Any]) -> E
other_list: list[Expr | str | Series]
other_list = [other] if not isinstance(other, list) else copy.copy(other) # type: ignore[arg-type]

other_list.insert(0, wrap_expr(self._pyexpr))
return F.concat_list(other_list)
other_exprs = [parse_into_expression(e) for e in other_list]
return wrap_expr(self._pyexpr.list_concat(other_exprs))

def get(
self,
Expand Down
6 changes: 0 additions & 6 deletions py-polars/tests/unit/datatypes/test_temporal.py
Original file line number Diff line number Diff line change
Expand Up @@ -1088,12 +1088,6 @@ def test_from_dict_tu_consistency() -> None:
def test_date_arr_concat() -> None:
expected = {"d": [[date(2000, 1, 1), date(2000, 1, 1)]]}

# type date
df = pl.DataFrame({"d": [date(2000, 1, 1)]})
assert (
df.select(pl.col("d").list.concat(pl.col("d"))).to_dict(as_series=False)
== expected
)
# type list[date]
df = pl.DataFrame({"d": [[date(2000, 1, 1)]]})
assert (
Expand Down
11 changes: 11 additions & 0 deletions py-polars/tests/unit/operations/namespaces/list/test_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -1362,3 +1362,14 @@ def test_list_sample_fraction_boundary_values_22024() -> None:
s.list.sample(fraction=0.0)
s.list.sample(fraction=1.0)
s.list.sample(fraction=pl.Series([0.0, 1.0]))


def test_list_concat_on_non_list_raises_25649() -> None:
"""Test that list.concat on a non-list column raises during schema collection."""
lf = pl.LazyFrame({"a": ["a", "b", "c"], "b": [1, 2, 3]})

with pytest.raises(
InvalidOperationError,
match="expected List data type for list operation",
):
lf.select(pl.col("a").list.concat(["a", "b"])).collect_schema()
Loading