55import pytest
66
77from narwhals ._plan import selectors as ncs
8+ from narwhals .exceptions import ColumnNotFoundError , InvalidOperationError
89
910pytest .importorskip ("pyarrow" )
1011pytest .importorskip ("numpy" )
1920if TYPE_CHECKING :
2021 from collections .abc import Sequence
2122
23+ from narwhals ._plan .typing import ColumnNameOrSelector , OneOrIterable
2224 from narwhals .typing import PythonLiteral
25+ from tests .conftest import Data
2326
2427
2528@pytest .fixture
@@ -44,12 +47,19 @@ def data_small() -> dict[str, Any]:
4447
4548
4649@pytest .fixture
47- def data_smaller (data_small : dict [str , Any ]) -> dict [str , Any ]:
50+ def data_small_af (data_small : dict [str , Any ]) -> dict [str , Any ]:
4851 """Use only columns `"a"-"f"`."""
4952 keep = {"a" , "b" , "c" , "d" , "e" , "f" }
5053 return {k : v for k , v in data_small .items () if k in keep }
5154
5255
56+ @pytest .fixture
57+ def data_small_dh (data_small : dict [str , Any ]) -> dict [str , Any ]:
58+ """Use only columns `"d"-"h"`."""
59+ keep = {"d" , "e" , "f" , "g" , "h" }
60+ return {k : v for k , v in data_small .items () if k in keep }
61+
62+
5363@pytest .fixture
5464def data_indexed () -> dict [str , Any ]:
5565 """Used in https://github.com/narwhals-dev/narwhals/pull/2528."""
@@ -472,9 +482,9 @@ def test_select(
472482def test_with_columns (
473483 expr : nwp .Expr | Sequence [nwp .Expr ],
474484 expected : dict [str , Any ],
475- data_smaller : dict [str , Any ],
485+ data_small_af : dict [str , Any ],
476486) -> None :
477- result = dataframe (data_smaller ).with_columns (expr )
487+ result = dataframe (data_small_af ).with_columns (expr )
478488 assert_equal_data (result , expected )
479489
480490
@@ -518,6 +528,62 @@ def test_row_is_py_literal(
518528 assert result == polars_result
519529
520530
531+ def test_drop_nulls (data_small_dh : Data ) -> None :
532+ df = dataframe (data_small_dh )
533+ expected : Data = {"d" : [], "e" : [], "f" : [], "g" : [], "h" : []}
534+ result = df .drop_nulls ()
535+ assert_equal_data (result , expected )
536+
537+
538+ def test_drop_nulls_invalid (data_small_dh : Data ) -> None :
539+ df = dataframe (data_small_dh )
540+ with pytest .raises (TypeError , match = r"cannot turn.+int.+into a selector" ):
541+ df .drop_nulls (123 ) # type: ignore[arg-type]
542+ with pytest .raises (
543+ InvalidOperationError , match = r"cannot turn.+col\('a'\).first\(\).+into a selector"
544+ ):
545+ df .drop_nulls (nwp .col ("a" ).first ()) # type: ignore[arg-type]
546+
547+ with pytest .raises (ColumnNotFoundError ):
548+ df .drop_nulls (["j" , "k" ])
549+
550+ with pytest .raises (ColumnNotFoundError ):
551+ df .drop_nulls (ncs .by_name ("j" , "k" ))
552+
553+ with pytest .raises (ColumnNotFoundError ):
554+ df .drop_nulls (ncs .by_index (- 999 ))
555+
556+
557+ DROP_ROW_1 : Data = {
558+ "d" : [7 , 8 ],
559+ "e" : [9 , 7 ],
560+ "f" : [False , None ],
561+ "g" : [None , False ],
562+ "h" : [None , True ],
563+ }
564+ KEEP_ROW_3 : Data = {"d" : [8 ], "e" : [7 ], "f" : [None ], "g" : [False ], "h" : [True ]}
565+
566+
567+ @pytest .mark .parametrize (
568+ ("subset" , "expected" ),
569+ [
570+ ("e" , DROP_ROW_1 ),
571+ (nwp .col ("e" ), DROP_ROW_1 ),
572+ (ncs .by_index (1 ), DROP_ROW_1 ),
573+ (ncs .integer (), DROP_ROW_1 ),
574+ ([ncs .numeric () | ~ ncs .boolean ()], DROP_ROW_1 ),
575+ (["g" , "h" ], KEEP_ROW_3 ),
576+ ([ncs .by_name ("g" , "h" ), "d" ], KEEP_ROW_3 ),
577+ ],
578+ )
579+ def test_drop_nulls_subset (
580+ data_small_dh : Data , subset : OneOrIterable [ColumnNameOrSelector ], expected : Data
581+ ) -> None :
582+ df = dataframe (data_small_dh )
583+ result = df .drop_nulls (subset )
584+ assert_equal_data (result , expected )
585+
586+
521587if TYPE_CHECKING :
522588 from typing_extensions import assert_type
523589
0 commit comments