|
| 1 | +import pytest |
| 2 | +import numpy as np |
| 3 | + |
| 4 | +pl = pytest.importorskip("polars") |
| 5 | +import pandas as pd # noqa: E402 |
| 6 | +from percentify import ( # noqa: E402 |
| 7 | + change, vif, missing, cv, outliers, r_squared, |
| 8 | + pca_variance, imbalance, difference, split, display, |
| 9 | +) |
| 10 | + |
| 11 | + |
| 12 | +# ===== DataFrame in -> polars DataFrame out ===== |
| 13 | + |
| 14 | +def test_vif_polars(): |
| 15 | + np.random.seed(0) |
| 16 | + base = np.random.randn(80) |
| 17 | + df = pl.DataFrame({ |
| 18 | + "a": base, |
| 19 | + "b": base * 2 + np.random.randn(80) * 0.1, |
| 20 | + "c": np.random.randn(80), |
| 21 | + }) |
| 22 | + result = vif(df) |
| 23 | + assert isinstance(result, pl.DataFrame) |
| 24 | + assert result.columns == ["feature", "VIF"] |
| 25 | + assert set(result["feature"].to_list()) == {"a", "b", "c"} |
| 26 | + |
| 27 | + |
| 28 | +def test_missing_polars_values(): |
| 29 | + df = pl.DataFrame({"salary": [1.0, None, 3.0, None], "age": [1.0, 2.0, None, 4.0]}) |
| 30 | + result = missing(df) |
| 31 | + assert isinstance(result, pl.DataFrame) |
| 32 | + d = dict(zip(result["column"].to_list(), result["missing_pct"].to_list())) |
| 33 | + assert d["salary"] == 50.0 |
| 34 | + assert d["age"] == 25.0 |
| 35 | + |
| 36 | + |
| 37 | +def test_cv_polars_dataframe(): |
| 38 | + result = cv(pl.DataFrame({"a": [10.0, 20, 30], "b": [1.0, 2, 3]})) |
| 39 | + assert isinstance(result, pl.DataFrame) |
| 40 | + assert result.columns == ["feature", "cv"] |
| 41 | + |
| 42 | + |
| 43 | +def test_outliers_polars_dataframe(): |
| 44 | + df = pl.DataFrame({"a": [1.0, 2, 3, 4, 5, 6, 100], "b": [1.0, 2, 3, 4, 5, 6, 7]}) |
| 45 | + result = outliers(df) |
| 46 | + assert isinstance(result, pl.DataFrame) |
| 47 | + assert result.columns == ["feature", "outlier_pct"] |
| 48 | + |
| 49 | + |
| 50 | +def test_pca_variance_polars(): |
| 51 | + np.random.seed(0) |
| 52 | + base = np.random.randn(80) |
| 53 | + df = pl.DataFrame({ |
| 54 | + "a": base + np.random.randn(80) * 0.3, |
| 55 | + "b": base + np.random.randn(80) * 0.3, |
| 56 | + "c": np.random.randn(80), |
| 57 | + }) |
| 58 | + result = pca_variance(df) |
| 59 | + assert isinstance(result, pl.DataFrame) |
| 60 | + assert result.columns == ["component", "variance_explained", "cumulative"] |
| 61 | + |
| 62 | + |
| 63 | +# ===== Series in -> scalar out ===== |
| 64 | + |
| 65 | +def test_cv_polars_series_returns_float(): |
| 66 | + assert isinstance(cv(pl.Series([10.0, 20, 30, 40, 50])), float) |
| 67 | + |
| 68 | + |
| 69 | +def test_outliers_polars_series_returns_float(): |
| 70 | + assert isinstance(outliers(pl.Series([1.0, 2, 3, 4, 5, 6, 100])), float) |
| 71 | + |
| 72 | + |
| 73 | +def test_r_squared_polars_series(): |
| 74 | + result = r_squared(pl.Series([1.0, 2, 3, 4, 5]), pl.Series([1.1, 1.9, 3.2, 3.8, 5.1])) |
| 75 | + assert isinstance(result, float) |
| 76 | + assert 90 < result < 100 |
| 77 | + |
| 78 | + |
| 79 | +# ===== Series in -> polars Series out ===== |
| 80 | + |
| 81 | +def test_change_polars_period_over_period(): |
| 82 | + result = change(pl.Series([100.0, 150, 90, 135])) |
| 83 | + assert isinstance(result, pl.Series) |
| 84 | + vals = result.to_list() |
| 85 | + assert vals[1] == 50.0 |
| 86 | + assert vals[2] == -40.0 |
| 87 | + |
| 88 | + |
| 89 | +def test_change_polars_two_columns(): |
| 90 | + result = change(pl.Series([100.0, 200, 50]), pl.Series([150.0, 150, 100])) |
| 91 | + assert isinstance(result, pl.Series) |
| 92 | + assert result.to_list() == [50.0, -25.0, 100.0] |
| 93 | + |
| 94 | + |
| 95 | +def test_difference_polars_two_columns(): |
| 96 | + result = difference(pl.Series([10.0, 50]), pl.Series([20.0, 50])) |
| 97 | + assert isinstance(result, pl.Series) |
| 98 | + assert result.to_list() == [66.67, 0.0] |
| 99 | + |
| 100 | + |
| 101 | +def test_split_polars_series(): |
| 102 | + result = split(200, pl.Series([1, 3])) |
| 103 | + assert isinstance(result, pl.Series) |
| 104 | + assert result.to_list() == [50.0, 150.0] |
| 105 | + |
| 106 | + |
| 107 | +def test_display_polars_series(): |
| 108 | + result = display(pl.Series([0.25, 0.5]), multiply=True) |
| 109 | + assert isinstance(result, pl.Series) |
| 110 | + assert result.to_list() == ["25.0%", "50.0%"] |
| 111 | + |
| 112 | + |
| 113 | +# ===== imbalance: polars DataFrame + summary preserved ===== |
| 114 | + |
| 115 | +def test_imbalance_polars_with_summary(): |
| 116 | + result = imbalance(pl.Series(["No"] * 850 + ["Yes"] * 150)) |
| 117 | + assert isinstance(result, pl.DataFrame) |
| 118 | + assert result.columns == ["class", "count", "pct"] |
| 119 | + summary = result.attrs["summary"] |
| 120 | + assert summary["majority_class"] == "No" |
| 121 | + assert summary["minority_class"] == "Yes" |
| 122 | + assert summary["imbalance_ratio"] == 5.67 |
| 123 | + |
| 124 | + |
| 125 | +# ===== regression: pandas in still returns pandas ===== |
| 126 | + |
| 127 | +def test_pandas_input_unchanged(): |
| 128 | + result = missing(pd.DataFrame({"a": [1.0, None], "b": [1.0, 2.0]})) |
| 129 | + assert isinstance(result, pd.DataFrame) |
| 130 | + |
| 131 | + |
| 132 | +def test_scalar_input_unchanged(): |
| 133 | + assert change(100, 150) == 50.0 |
| 134 | + assert display(0.45, multiply=True) == "45.0%" |
0 commit comments