This is an automated email from the ASF dual-hosted git repository.

uros-b pushed a commit to branch branch-4.x
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/branch-4.x by this push:
     new b5eb682ab081 [SPARK-46161][PS] Add axis=1 support for DataFrame.diff
b5eb682ab081 is described below

commit b5eb682ab081e26d238f8a81bad2a26c4353cdfc
Author: Devin Petersohn <[email protected]>
AuthorDate: Wed Aug 12 11:50:06 2026 -0700

    [SPARK-46161][PS] Add axis=1 support for DataFrame.diff
    
    ### What changes were proposed in this pull request?
    Implement column-wise differencing for `DataFrame.diff(axis=1)`.
    
    ### Why are the changes needed?
    It is a missing pandas API parameter.
    
    ### Does this PR introduce _any_ user-facing change?
    Yes, `DataFrame.diff(axis=1)` now works.
    
    ### How was this patch tested?
    CI
    
    ### Was this patch authored or co-authored using generative AI tooling?
    Co-authored-by: Claude Opus 4
    
    Closes #54516 from devin-petersohn/devin/diff-axis-columns.
    
    Authored-by: Devin Petersohn <[email protected]>
    Signed-off-by: Holden Karau <[email protected]>
    (cherry picked from commit a9689fb515d2b1f83efa705b2c1eb63c82330ba1)
    Signed-off-by: Uros Bojanic <[email protected]>
---
 python/pyspark/pandas/frame.py                     | 40 +++++++++++++++++-----
 .../pandas/tests/computation/test_compute.py       |  8 +++--
 2 files changed, 37 insertions(+), 11 deletions(-)

diff --git a/python/pyspark/pandas/frame.py b/python/pyspark/pandas/frame.py
index d196a85070bd..fdc7b6f7f49d 100644
--- a/python/pyspark/pandas/frame.py
+++ b/python/pyspark/pandas/frame.py
@@ -4946,7 +4946,6 @@ defaultdict(<class 'list'>, {'col..., 'col...})]
             lambda psser: psser._shift(periods, fill_value), 
should_resolve=True
         )
 
-    # TODO(SPARK-46161): axis should support 1 or 'columns' either at this 
moment
     def diff(self, periods: int = 1, axis: Axis = 0) -> "DataFrame":
         """
         First discrete difference of element.
@@ -4954,7 +4953,7 @@ defaultdict(<class 'list'>, {'col..., 'col...})]
         Calculates the difference of a DataFrame element compared with another 
element in the
         DataFrame (default is the element in the same column of the previous 
row).
 
-        .. note:: the current implementation of diff uses Spark's Window 
without
+        .. note:: When ``axis=0``, the current implementation of diff uses 
Spark's Window without
             specifying partition specification. This leads to moving all data 
into
             a single partition in a single machine and could cause serious
             performance degradation. Avoid this method with very large 
datasets.
@@ -4963,8 +4962,8 @@ defaultdict(<class 'list'>, {'col..., 'col...})]
         ----------
         periods : int, default 1
             Periods to shift for calculating difference, accepts negative 
values.
-        axis : int, default 0 or 'index'
-            Can only be set to 0 now.
+        axis : {0 or 'index', 1 or 'columns'}, default 0
+            Take difference over rows (0) or columns (1).
 
         Returns
         -------
@@ -5014,12 +5013,37 @@ defaultdict(<class 'list'>, {'col..., 'col...})]
         3 -1.0 -2.0  -9.0
         4 -1.0 -3.0 -11.0
         5  NaN  NaN   NaN
+
+        Difference with previous column
+
+        >>> df.diff(axis=1)
+            a  b   c
+        0 NaN  0   0
+        1 NaN -1   3
+        2 NaN -1   7
+        3 NaN -1  13
+        4 NaN  0  20
+        5 NaN  2  28
         """
         axis = validate_axis(axis)
-        if axis != 0:
-            raise NotImplementedError('axis should be either 0 or "index" 
currently.')
-
-        return self._apply_series_op(lambda psser: psser._diff(periods), 
should_resolve=True)
+        if axis == 0:
+            return self._apply_series_op(lambda psser: psser._diff(periods), 
should_resolve=True)
+        else:
+            column_labels = self._internal.column_labels
+            data_col_names = self._internal.data_spark_column_names
+            new_columns: list[PySparkColumn] = []
+            for i, label in enumerate(column_labels):
+                prev_idx = i - periods
+                if 0 <= prev_idx < len(column_labels):
+                    prev_label = column_labels[prev_idx]
+                    cur_col = self._internal.spark_column_for(label)
+                    prev_col = self._internal.spark_column_for(prev_label)
+                    new_columns.append(cur_col - prev_col)
+                else:
+                    col_type = self._internal.spark_type_for(label)
+                    
new_columns.append(F.lit(None).cast(col_type).alias(data_col_names[i]))
+            internal = self._internal.with_new_columns(new_columns)
+            return DataFrame(internal)
 
     def nunique(
         self,
diff --git a/python/pyspark/pandas/tests/computation/test_compute.py 
b/python/pyspark/pandas/tests/computation/test_compute.py
index 4538509cd4d3..924046fe256b 100644
--- a/python/pyspark/pandas/tests/computation/test_compute.py
+++ b/python/pyspark/pandas/tests/computation/test_compute.py
@@ -198,9 +198,11 @@ class FrameComputeMixin:
         msg = "should be an int"
         with self.assertRaisesRegex(TypeError, msg):
             psdf.diff(1.5)
-        msg = 'axis should be either 0 or "index" currently.'
-        with self.assertRaisesRegex(NotImplementedError, msg):
-            psdf.diff(axis=1)
+
+        # axis=1: difference across columns
+        self.assert_eq(pdf.diff(axis=1), psdf.diff(axis=1))
+        self.assert_eq(pdf.diff(periods=2, axis=1), psdf.diff(periods=2, 
axis=1))
+        self.assert_eq(pdf.diff(periods=-1, axis=1), psdf.diff(periods=-1, 
axis=1))
 
         # multi-index columns
         columns = pd.MultiIndex.from_tuples([("x", "Col1"), ("x", "Col2"), 
("y", "Col3")])


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to