Repository: narwhals-dev/narwhals
Pull request #1140 (2024-10-05 10:41:52)

fix: pyarrow unpivot upcast numeric
## What type of PR is this? (check all applicable)

- [ ] 💾 Refactor
- [ ] ✨ Feature
- [x] 🐛 Bug Fix
- [ ] 🔧 Optimization
- [ ] 📝 Documentation
- [ ] ✅ Test
- [ ] 🐳 Other

## Related issues 

- Related issue #1139

## Checklist

- [x] Code follows style guide (ruff)
- [x] Tests added 
- [ ] Documented the changes

## If you have comments or can explain your changes, please do so below.

For plotly, this is enough (as one can't plot string values).

Yet I left a TODO, up for discussion, in case we want to allow numeric to be upcasted to string as well - drawback is that we should do that manually.


Patch:
diff --git a/docs/backcompat.md b/docs/backcompat.md
index 3c4a19a7..55f31aad 100644
--- a/docs/backcompat.md
+++ b/docs/backcompat.md
@@ -74,8 +74,8 @@ users of `narwhals.stable.v1` will have their code unaffected.
 Which should you use? In general we recommend:
 
 - When prototyping, use `import narwhals as nw`, so you can iterate quickly.
-- Once you're happy with what you've got and what to release something production-ready and stable,
-  when switch out your `import narwhals as nw` usage for `import narwhals.stable.v1 as nw`.
+- Once you're happy with what you've got and want to release something production-ready and stable,
+  then switch out your `import narwhals as nw` usage for `import narwhals.stable.v1 as nw`.
 
 ## Exceptions
 
diff --git a/narwhals/_arrow/dataframe.py b/narwhals/_arrow/dataframe.py
index 905ada59..4bab24a2 100644
--- a/narwhals/_arrow/dataframe.py
+++ b/narwhals/_arrow/dataframe.py
@@ -682,6 +682,11 @@ class ArrowDataFrame:
 
         n_rows = len(self)
 
+        promote_kwargs = (
+            {"promote_options": "permissive"}
+            if self._backend_version >= (14, 0, 0)
+            else {}
+        )
         return self._from_native_frame(
             pa.concat_tables(
                 [
@@ -694,6 +699,9 @@ class ArrowDataFrame:
                         names=[*index_, variable_name, value_name],
                     )
                     for on_col in on_
-                ]
+                ],
+                **promote_kwargs,
             )
         )
+        # TODO(Unassigned): Even with promote_options="permissive", pyarrow does not
+        # upcast numeric to non-numeric (e.g. string) datatypes
diff --git a/narwhals/_pandas_like/group_by.py b/narwhals/_pandas_like/group_by.py
index f2038346..366a52e1 100644
--- a/narwhals/_pandas_like/group_by.py
+++ b/narwhals/_pandas_like/group_by.py
@@ -212,7 +212,9 @@ def agg_pandas(  # noqa: PLR0915
             result_aggs = result_simple_aggs
         else:
             # No aggregation provided
-            result_aggs = native_namespace.DataFrame(grouped.groups.keys(), columns=keys)
+            result_aggs = native_namespace.DataFrame(
+                list(grouped.groups.keys()), columns=keys
+            )
         return from_dataframe(result_aggs.loc[:, output_names])
 
     if dataframe_is_empty:


Test patch:
diff --git a/tests/frame/unpivot_test.py b/tests/frame/unpivot_test.py
index 2adad646..d169d5ca 100644
--- a/tests/frame/unpivot_test.py
+++ b/tests/frame/unpivot_test.py
@@ -1,11 +1,19 @@
 from __future__ import annotations
 
+from typing import TYPE_CHECKING
+from typing import Any
+
+import pyarrow as pa
 import pytest
 
 import narwhals.stable.v1 as nw
+from narwhals.utils import parse_version
 from tests.utils import Constructor
 from tests.utils import compare_dicts
 
+if TYPE_CHECKING:
+    from narwhals.stable.v1.dtypes import DType
+
 data = {
     "a": ["x", "y", "z"],
     "b": [1, 3, 5],
@@ -70,3 +78,29 @@ def test_unpivot_default_var_value_names(constructor: Constructor) -> None:
     result = df.unpivot(on=["b", "c"], index=["a"])
 
     assert result.collect_schema().names()[-2:] == ["variable", "value"]
+
+
+@pytest.mark.parametrize(
+    ("data", "expected_dtypes"),
+    [
+        (
+            {"idx": [0, 1], "a": [1, 2], "b": [1.5, 2.5]},
+            [nw.Int64(), nw.String(), nw.Float64()],
+        ),
+    ],
+)
+def test_unpivot_mixed_types(
+    request: pytest.FixtureRequest,
+    constructor: Constructor,
+    data: dict[str, Any],
+    expected_dtypes: list[DType],
+) -> None:
+    if "dask" in str(constructor) or (
+        "pyarrow_table" in str(constructor)
+        and parse_version(pa.__version__) < parse_version("14.0.0")
+    ):
+        request.applymarker(pytest.mark.xfail)
+    df = nw.from_native(constructor(data))
+    result = df.unpivot(on=["a", "b"], index="idx")
+
+    assert result.collect_schema().dtypes() == expected_dtypes