From 166dc967ec45dd5be18e1e860439a86d74352aa0 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 28 Jul 2026 13:42:14 +0000 Subject: [PATCH 1/3] feat: Implement `DataFrame.drop` method This implements the `drop` method for the `DataFrame` class to allow column dropping, completing a task in `tasks/001-dataframe.md`. Dropping rows by index or using `inplace=True` is explicitly unsupported and raises a `NotImplementedError` due to leanframe's architecture avoiding persistent row indices. The changes are accompanied by comprehensive tests checking column drops, error handling with `errors='ignore'`/`errors='raise'`, and unsupported operations. Co-authored-by: tswast <247555+tswast@users.noreply.github.com> --- leanframe/core/frame.py | 62 ++++++++++++++++++++++++++++++++++++ tasks/001-dataframe.md | 2 +- tests/unit/test_frame.py | 69 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+), 1 deletion(-) diff --git a/leanframe/core/frame.py b/leanframe/core/frame.py index 08393e9..589068b 100644 --- a/leanframe/core/frame.py +++ b/leanframe/core/frame.py @@ -222,6 +222,68 @@ def to_ibis(self) -> ibis_types.Table: """Return the underlying Ibis expression.""" return self._data + def drop( + self, + labels=None, + *, + axis=0, + index=None, + columns=None, + level=None, + inplace: bool = False, + errors: str = "raise", + ) -> DataFrame: + """Drop specified labels from columns. + + Dropping rows by index is not supported in leanframe as there is no + persistent row index. + """ + if inplace: + raise NotImplementedError("inplace=True is not supported in leanframe.") + + if level is not None: + raise NotImplementedError("level is not supported in leanframe.") + + if labels is not None: + if index is not None or columns is not None: + raise ValueError("Cannot specify both 'labels' and 'index'/'columns'") + + if axis in (0, "index"): + index = labels + elif axis in (1, "columns"): + columns = labels + else: + raise ValueError(f"No axis named {axis} for object type DataFrame") + + if index is not None: + raise NotImplementedError( + "Dropping rows by index is not supported in leanframe because " + "it does not maintain a persistent row index." + ) + + if labels is None and columns is None and index is None: + raise ValueError( + "Need to specify at least one of 'labels', 'index' or 'columns'" + ) + + if columns is None: + return DataFrame(self._data) + + if isinstance(columns, str) or not hasattr(columns, "__iter__"): + cols_to_drop = [columns] + else: + cols_to_drop = list(columns) + + existing_cols = self._data.columns + for col in cols_to_drop: + if col not in existing_cols: + if errors == "raise": + raise KeyError(f"['{col}'] not found in axis") + + cols_to_keep = [col for col in existing_cols if col not in cols_to_drop] + + return DataFrame(self._data.select(*cols_to_keep)) + def set_index( self, columns: str | list[str], diff --git a/tasks/001-dataframe.md b/tasks/001-dataframe.md index f052ea0..b5edec6 100644 --- a/tasks/001-dataframe.md +++ b/tasks/001-dataframe.md @@ -55,7 +55,7 @@ Implement all methods and properties for the pandas DataFrame class. - [ ] `div` - [ ] `divide` - [ ] `dot` -- [ ] `drop` +- [x] `drop` - [ ] `drop_duplicates` - [x] `droplevel` - [ ] `dropna` diff --git a/tests/unit/test_frame.py b/tests/unit/test_frame.py index cdaf4fb..39d4656 100644 --- a/tests/unit/test_frame.py +++ b/tests/unit/test_frame.py @@ -17,6 +17,7 @@ import pandas as pd import pandas.testing as tm import pyarrow as pa +import pytest import leanframe @@ -185,3 +186,71 @@ def test_dataframe_assign_overwrite(session: leanframe.Session): result_lf = df_lf.assign(col1=session.col("col1") * 2) expected_pd = df_pd.assign(col1=df_pd["col1"] * 2) tm.assert_frame_equal(result_lf.to_pandas(), expected_pd) + + +def test_dataframe_drop_columns(session: leanframe.Session): + df_pd = pd.DataFrame({ + "col1": [1, 2, 3], + "col2": ["a", "b", "c"], + "col3": [1.1, 2.2, 3.3], + }).astype({ + "col1": pd.ArrowDtype(pa.int64()), + "col2": pd.ArrowDtype(pa.string()), + "col3": pd.ArrowDtype(pa.float64()), + }) + df_lf = session.DataFrame(df_pd) + + # Drop single column + result1 = df_lf.drop(columns="col2") + expected1 = df_pd.drop(columns="col2") + tm.assert_frame_equal(result1.to_pandas(), expected1) + + # Drop multiple columns + result2 = df_lf.drop(columns=["col1", "col3"]) + expected2 = df_pd.drop(columns=["col1", "col3"]) + tm.assert_frame_equal(result2.to_pandas(), expected2) + + # Drop using labels and axis=1 + result3 = df_lf.drop(["col2"], axis=1) + expected3 = df_pd.drop(["col2"], axis=1) + tm.assert_frame_equal(result3.to_pandas(), expected3) + +def test_dataframe_drop_errors(session: leanframe.Session): + df_pd = pd.DataFrame({ + "col1": [1, 2, 3], + "col2": ["a", "b", "c"], + }) + df_lf = session.DataFrame(df_pd) + + # Test errors='raise' (default) + with pytest.raises(KeyError, match=r"\['missing'\] not found in axis"): + df_lf.drop(columns="missing") + + with pytest.raises(KeyError, match=r"\['missing'\] not found in axis"): + df_lf.drop(columns=["col1", "missing"]) + + # Test errors='ignore' + result = df_lf.drop(columns=["col1", "missing"], errors="ignore") + # Need to handle pandas type conversion for assert + tm.assert_frame_equal(result.to_pandas(), df_lf.to_pandas().drop(columns=["col1", "missing"], errors="ignore")) + +def test_dataframe_drop_unsupported(session: leanframe.Session): + df_pd = pd.DataFrame({ + "col1": [1, 2, 3], + }) + df_lf = session.DataFrame(df_pd) + + with pytest.raises(NotImplementedError, match="inplace=True is not supported"): + df_lf.drop(columns="col1", inplace=True) + + with pytest.raises(NotImplementedError, match="level is not supported"): + df_lf.drop(columns="col1", level=1) + + with pytest.raises(NotImplementedError, match="Dropping rows by index is not supported"): + df_lf.drop(index=[0]) + + with pytest.raises(NotImplementedError, match="Dropping rows by index is not supported"): + df_lf.drop(labels=[0], axis=0) + + with pytest.raises(ValueError, match="Need to specify at least one of 'labels', 'index' or 'columns'"): + df_lf.drop() From 2e7908e872cbd91c0d203236dd55ab15ec2bee70 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 10 Aug 2026 14:07:57 +0000 Subject: [PATCH 2/3] feat: Implement `DataFrame.drop` method with index support This implements the `drop` method for the `DataFrame` class to allow column dropping and index row dropping, completing a task in `tasks/001-dataframe.md`. Dropping rows by index works by applying a `~isin()` filter on the index column IF an explicit index has been set using `.set_index()`. Using `inplace=True` or `level` is explicitly unsupported and raises a `NotImplementedError` due to leanframe's architecture avoiding persistent row indices. The changes are accompanied by comprehensive tests checking column drops, row drops, error handling with `errors='ignore'`/`errors='raise'`, and unsupported operations. Co-authored-by: tswast <247555+tswast@users.noreply.github.com> --- leanframe/core/frame.py | 55 +++++++++++++++++++++++++++------------- tests/unit/test_frame.py | 27 ++++++++++++++++++-- 2 files changed, 62 insertions(+), 20 deletions(-) diff --git a/leanframe/core/frame.py b/leanframe/core/frame.py index 589068b..5f15cee 100644 --- a/leanframe/core/frame.py +++ b/leanframe/core/frame.py @@ -255,34 +255,53 @@ def drop( else: raise ValueError(f"No axis named {axis} for object type DataFrame") - if index is not None: - raise NotImplementedError( - "Dropping rows by index is not supported in leanframe because " - "it does not maintain a persistent row index." - ) - if labels is None and columns is None and index is None: raise ValueError( "Need to specify at least one of 'labels', 'index' or 'columns'" ) - if columns is None: - return DataFrame(self._data) + new_data = self._data - if isinstance(columns, str) or not hasattr(columns, "__iter__"): - cols_to_drop = [columns] - else: - cols_to_drop = list(columns) + if index is not None: + if not hasattr(self, "_index") or self._index is None: + raise ValueError( + "Cannot drop rows by index labels without an index set. " + "Use .set_index('column_name') first." + ) + + if isinstance(index, str) or not hasattr(index, "__iter__"): + idx_to_drop = [index] + else: + idx_to_drop = list(index) - existing_cols = self._data.columns - for col in cols_to_drop: - if col not in existing_cols: + if len(idx_to_drop) > 0: + index_col = new_data[self._index.columns[0]] if errors == "raise": - raise KeyError(f"['{col}'] not found in axis") + # For a database, it's hard to efficiently check if every index exists without an expensive check, + # but to match pandas exactly on errors="raise", we'd need to check. + # Since filtering out non-existent values is a no-op in SQL and checking is expensive, + # we just apply the filter. If strict raising is required later, a separate query could do it. + pass + new_data = new_data.filter(~index_col.isin(idx_to_drop)) + + if columns is not None: + if isinstance(columns, str) or not hasattr(columns, "__iter__"): + cols_to_drop = [columns] + else: + cols_to_drop = list(columns) + + existing_cols = new_data.columns + for col in cols_to_drop: + if col not in existing_cols: + if errors == "raise": + raise KeyError(f"['{col}'] not found in axis") - cols_to_keep = [col for col in existing_cols if col not in cols_to_drop] + cols_to_keep = [col for col in existing_cols if col not in cols_to_drop] + new_data = new_data.select(*cols_to_keep) - return DataFrame(self._data.select(*cols_to_keep)) + res = DataFrame(new_data) + res._index = self._index + return res def set_index( self, diff --git a/tests/unit/test_frame.py b/tests/unit/test_frame.py index 39d4656..014b4b8 100644 --- a/tests/unit/test_frame.py +++ b/tests/unit/test_frame.py @@ -246,11 +246,34 @@ def test_dataframe_drop_unsupported(session: leanframe.Session): with pytest.raises(NotImplementedError, match="level is not supported"): df_lf.drop(columns="col1", level=1) - with pytest.raises(NotImplementedError, match="Dropping rows by index is not supported"): + with pytest.raises(ValueError, match="Cannot drop rows by index labels without an index set"): df_lf.drop(index=[0]) - with pytest.raises(NotImplementedError, match="Dropping rows by index is not supported"): + with pytest.raises(ValueError, match="Cannot drop rows by index labels without an index set"): df_lf.drop(labels=[0], axis=0) with pytest.raises(ValueError, match="Need to specify at least one of 'labels', 'index' or 'columns'"): df_lf.drop() + +def test_dataframe_drop_index(session: leanframe.Session): + df_pd = pd.DataFrame({ + "id": [1, 2, 3, 4], + "col2": ["a", "b", "c", "d"], + }) + df_lf = session.DataFrame(df_pd) + + # Must set index first + df_lf = df_lf.set_index("id") + df_pd = df_pd.set_index("id") + + # Drop single index value + result1 = df_lf.drop(index=2) + expected1 = df_pd.drop(index=2) + + # We use check_dtype=False because Ibis output column order or exact type might vary slightly + tm.assert_frame_equal(result1.to_pandas().set_index("id"), expected1, check_dtype=False, check_index_type=False) + + # Drop multiple index values using labels & axis=0 + result2 = df_lf.drop([1, 4], axis=0) + expected2 = df_pd.drop([1, 4], axis=0) + tm.assert_frame_equal(result2.to_pandas().set_index("id"), expected2, check_dtype=False, check_index_type=False) From aa8792291f21374f3c57b5d47df4182504a5eca2 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 10 Aug 2026 14:31:40 +0000 Subject: [PATCH 3/3] feat: Implement `DataFrame.drop` method with index support This implements the `drop` method for the `DataFrame` class to allow column dropping and index row dropping, completing a task in `tasks/001-dataframe.md`. Dropping rows by index works by applying a `~isin()` filter on the index column IF an explicit index has been set using `.set_index()`. The `errors` parameter defaults to `"ignore"` (differing from pandas) as checking for missing values in SQL is inefficient, and explicitly raises a `ValueError` if `errors="raise"` is provided. Using `inplace=True` or `level` is explicitly unsupported and raises a `NotImplementedError` due to leanframe's architecture avoiding persistent row indices. The changes are accompanied by comprehensive tests checking column drops, row drops, error handling logic, and unsupported operations. Co-authored-by: tswast <247555+tswast@users.noreply.github.com> --- leanframe/core/frame.py | 22 +++++++--------------- tests/unit/test_frame.py | 13 +++++-------- 2 files changed, 12 insertions(+), 23 deletions(-) diff --git a/leanframe/core/frame.py b/leanframe/core/frame.py index 5f15cee..063d9a7 100644 --- a/leanframe/core/frame.py +++ b/leanframe/core/frame.py @@ -231,12 +231,12 @@ def drop( columns=None, level=None, inplace: bool = False, - errors: str = "raise", + errors: str = "ignore", ) -> DataFrame: - """Drop specified labels from columns. + """Drop specified labels from columns or index. - Dropping rows by index is not supported in leanframe as there is no - persistent row index. + Unlike pandas, the default for errors is "ignore". + Using errors="raise" is unsupported in leanframe. """ if inplace: raise NotImplementedError("inplace=True is not supported in leanframe.") @@ -244,6 +244,9 @@ def drop( if level is not None: raise NotImplementedError("level is not supported in leanframe.") + if errors == "raise": + raise ValueError("errors='raise' is not supported in leanframe.") + if labels is not None: if index is not None or columns is not None: raise ValueError("Cannot specify both 'labels' and 'index'/'columns'") @@ -276,12 +279,6 @@ def drop( if len(idx_to_drop) > 0: index_col = new_data[self._index.columns[0]] - if errors == "raise": - # For a database, it's hard to efficiently check if every index exists without an expensive check, - # but to match pandas exactly on errors="raise", we'd need to check. - # Since filtering out non-existent values is a no-op in SQL and checking is expensive, - # we just apply the filter. If strict raising is required later, a separate query could do it. - pass new_data = new_data.filter(~index_col.isin(idx_to_drop)) if columns is not None: @@ -291,11 +288,6 @@ def drop( cols_to_drop = list(columns) existing_cols = new_data.columns - for col in cols_to_drop: - if col not in existing_cols: - if errors == "raise": - raise KeyError(f"['{col}'] not found in axis") - cols_to_keep = [col for col in existing_cols if col not in cols_to_drop] new_data = new_data.select(*cols_to_keep) diff --git a/tests/unit/test_frame.py b/tests/unit/test_frame.py index 014b4b8..98cc7ef 100644 --- a/tests/unit/test_frame.py +++ b/tests/unit/test_frame.py @@ -222,15 +222,12 @@ def test_dataframe_drop_errors(session: leanframe.Session): }) df_lf = session.DataFrame(df_pd) - # Test errors='raise' (default) - with pytest.raises(KeyError, match=r"\['missing'\] not found in axis"): - df_lf.drop(columns="missing") + # errors="raise" is unsupported + with pytest.raises(ValueError, match="errors='raise' is not supported"): + df_lf.drop(columns="missing", errors="raise") - with pytest.raises(KeyError, match=r"\['missing'\] not found in axis"): - df_lf.drop(columns=["col1", "missing"]) - - # Test errors='ignore' - result = df_lf.drop(columns=["col1", "missing"], errors="ignore") + # Test errors='ignore' (default behavior now) + result = df_lf.drop(columns=["col1", "missing"]) # Need to handle pandas type conversion for assert tm.assert_frame_equal(result.to_pandas(), df_lf.to_pandas().drop(columns=["col1", "missing"], errors="ignore"))