Skip to content

Commit e53ce17

Browse files
authored
Merge pull request #206 from mystic74/fix/dataframe-mutation-categorical-nan
Fix DataFrame mutation bug by copying data on initialization
2 parents 178f81d + ef3a7e3 commit e53ce17

2 files changed

Lines changed: 91 additions & 1 deletion

File tree

tableone/tableone.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ def _repr_html_(self) -> str:
304304
def initialize_core_attributes(self, data, columns, categorical, continuous, groupby,
305305
nonnormal, min_max, pval, pval_adjust, htest_name,
306306
htest, missing, ddof, rename, sort, limit, order,
307-
label_suffix, decimals, smd, overall, row_percent,
307+
label_suffix, decimals, smd, overall, row_percent,
308308
dip_test, normal_test, tukey_test, pval_threshold,
309309
include_null, pval_digits, ttest_equal_var,
310310
show_histograms, clip_histograms):
@@ -348,6 +348,8 @@ def initialize_core_attributes(self, data, columns, categorical, continuous, gro
348348
self._warnings = {}
349349

350350
if self._categorical and self._include_null:
351+
# Create a copy to avoid mutating the user's original DataFrame
352+
data = data.copy()
351353
data[self._categorical] = handle_categorical_nulls(data[self._categorical], self._categorical)
352354

353355
self._groupbylvls = get_groups(data, self._groupby, self._order, self._reserved_columns)

tests/unit/test_tableone.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1396,6 +1396,94 @@ def test_handle_categorical_nulls_does_not_affect_continuous():
13961396
assert result['cat'].iloc[2] == 'None'
13971397

13981398

1399+
def test_handle_categorical_nulls_does_not_mutate_original():
1400+
"""
1401+
Test that handle_categorical_nulls does not mutate the original DataFrame.
1402+
This tests the preprocessor function directly.
1403+
"""
1404+
df = pd.DataFrame({
1405+
'cat': [1, 2, np.nan],
1406+
'other': ['a', 'b', 'c']
1407+
})
1408+
1409+
df_original = df.copy()
1410+
result = handle_categorical_nulls(df, categorical=['cat'])
1411+
1412+
pd.testing.assert_frame_equal(df, df_original, check_dtype=True)
1413+
assert result['cat'].iloc[2] == 'None'
1414+
1415+
1416+
def test_tableone_does_not_mutate_input_with_numeric_categorical_nan():
1417+
"""
1418+
Test that TableOne does not mutate the original DataFrame when processing
1419+
numeric categorical variables with NaN values.
1420+
1421+
Regression test for issue where NaN values were converted to string 'None'
1422+
in the original DataFrame.
1423+
"""
1424+
df = pd.DataFrame({
1425+
'id': [1, 2, 3],
1426+
'category_col': [1, np.nan, 2]
1427+
})
1428+
1429+
df_original = df.copy()
1430+
1431+
TableOne(df, categorical=['category_col'], pval=False)
1432+
1433+
pd.testing.assert_frame_equal(df, df_original, check_dtype=True)
1434+
1435+
1436+
def test_tableone_does_not_mutate_input_with_string_categorical_nan():
1437+
"""
1438+
Test that TableOne does not mutate the original DataFrame when processing
1439+
string categorical variables with NaN values.
1440+
"""
1441+
df = pd.DataFrame({
1442+
'id': [1, 2, 3],
1443+
'category_col': ['A', np.nan, 'B']
1444+
})
1445+
1446+
df_original = df.copy()
1447+
1448+
TableOne(df, categorical=['category_col'], pval=False)
1449+
1450+
pd.testing.assert_frame_equal(df, df_original, check_dtype=True)
1451+
1452+
1453+
def test_tableone_does_not_mutate_input_with_groupby_and_pval():
1454+
"""
1455+
Test that TableOne does not mutate the original DataFrame when using
1456+
groupby and pval with categorical variables containing NaN.
1457+
"""
1458+
df = pd.DataFrame({
1459+
'group': ['A', 'B', 'A', 'B'],
1460+
'cat_var': [1, 2, np.nan, 3]
1461+
})
1462+
1463+
df_original = df.copy()
1464+
1465+
TableOne(df, categorical=['cat_var'], groupby='group', pval=True)
1466+
1467+
pd.testing.assert_frame_equal(df, df_original, check_dtype=True)
1468+
1469+
1470+
def test_tableone_does_not_mutate_input_with_include_null_false():
1471+
"""
1472+
Test that TableOne does not mutate the original DataFrame even when
1473+
include_null=False.
1474+
"""
1475+
df = pd.DataFrame({
1476+
'id': [1, 2, 3],
1477+
'category_col': [1, np.nan, 2]
1478+
})
1479+
1480+
df_original = df.copy()
1481+
1482+
TableOne(df, categorical=['category_col'], include_null=False, pval=False)
1483+
1484+
pd.testing.assert_frame_equal(df, df_original, check_dtype=True)
1485+
1486+
13991487
def test_pval_digits_custom_formatting():
14001488
df = pd.DataFrame({
14011489
'group': ['A', 'A', 'B', 'B', 'B'],

0 commit comments

Comments
 (0)