Repository: narwhals-dev/narwhals

Issue:
bug: `from_dict` uses pandas' index alignment rules (as opposed to Narwhals)
```python
In [3]: import narwhals as nw
   ...: 
   ...: df = pd.DataFrame({'a': [1,2,3], 'b': [4,5,6]})
   ...: 
   ...: print(nw.from_dict({'a': df['a'], 'b': df['a'].sort_values(ascending=False)}, native_namespace=pd).to_native())
   ...: 
   a  b
0  1  1
1  2  2
2  3  3
```

expected:
```
   a  b
0  1  3
1  2  2
2  3  1
```

it should follow the left-hand-rule described in https://narwhals-dev.github.io/narwhals/pandas_like_concepts/pandas_index/, which is what we do elsewhere in Narwhals

Discussion:
MarcoGorelli: thanks for your review!

Patch:
diff --git a/.github/workflows/extremes.yml b/.github/workflows/extremes.yml
index 4b89bb63..3db40715 100644
--- a/.github/workflows/extremes.yml
+++ b/.github/workflows/extremes.yml
@@ -92,7 +92,7 @@ jobs:
       matrix:
         python-version: ["3.13"]
         os: [ubuntu-latest]
-    if: github.event.pull_request.head.repo.full_name == github.repository
+    if: github.event_name == 'push' && github.ref == 'refs/heads/main' || github.event.pull_request.head.repo.full_name == github.repository
     runs-on: ${{ matrix.os }}
     steps:
       - uses: actions/checkout@v4
diff --git a/narwhals/functions.py b/narwhals/functions.py
index 368603f6..5e14f190 100644
--- a/narwhals/functions.py
+++ b/narwhals/functions.py
@@ -10,6 +10,7 @@ from typing import Protocol
 from typing import TypeVar
 from typing import Union
 
+from narwhals._pandas_like.utils import validate_column_comparand
 from narwhals.dataframe import DataFrame
 from narwhals.dataframe import LazyFrame
 from narwhals.translate import from_native
@@ -328,6 +329,9 @@ def from_dict(
 ) -> DataFrame[Any]:
     """Instantiate DataFrame from dictionary.
 
+    Indexes (if present, for pandas-like backends) are aligned following
+    the [left-hand-rule](https://narwhals-dev.github.io/narwhals/pandas_like_concepts/pandas_index/).
+
     Notes:
         For pandas-like dataframes, conversion to schema is applied after dataframe
         creation.
@@ -433,7 +437,24 @@ def _from_dict_impl(
         Implementation.MODIN,
         Implementation.CUDF,
     }:
-        native_frame = native_namespace.DataFrame.from_dict(data)
+        aligned_data = {}
+        left_most_series = None
+        for key, native_series in data.items():
+            if isinstance(native_series, native_namespace.Series):
+                compliant_series = from_native(
+                    native_series, series_only=True
+                )._compliant_series
+                if left_most_series is None:
+                    left_most_series = compliant_series
+                    aligned_data[key] = native_series
+                else:
+                    aligned_data[key] = validate_column_comparand(
+                        left_most_series, compliant_series
+                    )[1]
+            else:
+                aligned_data[key] = native_series
+
+        native_frame = native_namespace.DataFrame.from_dict(aligned_data)
 
         if schema:
             from narwhals._pandas_like.utils import (
diff --git a/narwhals/stable/v1/__init__.py b/narwhals/stable/v1/__init__.py
index 11019233..23b6f798 100644
--- a/narwhals/stable/v1/__init__.py
+++ b/narwhals/stable/v1/__init__.py
@@ -3175,6 +3175,9 @@ def from_dict(
 ) -> DataFrame[Any]:
     """Instantiate DataFrame from dictionary.
 
+    Indexes (if present, for pandas-like backends) are aligned following
+    the [left-hand-rule](https://narwhals-dev.github.io/narwhals/pandas_like_concepts/pandas_index/).
+
     Notes:
         For pandas-like dataframes, conversion to schema is applied after dataframe
         creation.


Test patch:
diff --git a/tests/from_dict_test.py b/tests/from_dict_test.py
index 24167e7b..86fe07ed 100644
--- a/tests/from_dict_test.py
+++ b/tests/from_dict_test.py
@@ -2,6 +2,7 @@ from __future__ import annotations
 
 from datetime import datetime
 
+import pandas as pd
 import pytest
 
 import narwhals as nw
@@ -78,3 +79,13 @@ def test_from_dict_v1(constructor: Constructor, request: pytest.FixtureRequest)
 def test_from_dict_empty() -> None:
     with pytest.raises(ValueError, match="empty"):
         nw.from_dict({})
+
+
+def test_alignment() -> None:
+    # https://github.com/narwhals-dev/narwhals/issues/1474
+    df = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
+    result = nw.from_dict(
+        {"a": df["a"], "b": df["a"].sort_values(ascending=False)}, native_namespace=pd
+    ).to_native()
+    expected = pd.DataFrame({"a": [1, 2, 3], "b": [3, 2, 1]})
+    pd.testing.assert_frame_equal(result, expected)