diff --git a/spyder/plugins/variableexplorer/widgets/dataframeeditor.py b/spyder/plugins/variableexplorer/widgets/dataframeeditor.py index 4098e2d867d..da26604d63e 100644 --- a/spyder/plugins/variableexplorer/widgets/dataframeeditor.py +++ b/spyder/plugins/variableexplorer/widgets/dataframeeditor.py @@ -63,7 +63,7 @@ from spyder.utils.palette import QStylePalette -# Supported Numbers and complex numbers +# Supported real and complex number types REAL_NUMBER_TYPES = (float, int, np.int64, np.int32) COMPLEX_NUMBER_TYPES = (complex, np.complex64, np.complex128) @@ -91,6 +91,18 @@ BACKGROUND_MISC_ALPHA = 0.3 +def is_any_real_numeric_dtype(dtype) -> bool: + """ + Test whether a Pandas dtype is a real numeric type. + """ + try: + import pandas.api.types + return pandas.api.types.is_any_real_numeric_dtype(dtype) + except Exception: + # Pandas version 1 + return dtype in REAL_NUMBER_TYPES + + def bool_false_check(value): """ Used to convert bool entrance to false. @@ -250,8 +262,11 @@ def max_min_col_update(self): # the maximum of a column. # Fixes spyder-ide/spyder#17145 try: - if col.dtype in REAL_NUMBER_TYPES + COMPLEX_NUMBER_TYPES: - if col.dtype in REAL_NUMBER_TYPES: + if ( + is_any_real_numeric_dtype(col.dtype) + or col.dtype in COMPLEX_NUMBER_TYPES + ): + if is_any_real_numeric_dtype(col.dtype): vmax = col.max(skipna=True) vmin = col.min(skipna=True) else: diff --git a/spyder/plugins/variableexplorer/widgets/tests/test_dataframeeditor.py b/spyder/plugins/variableexplorer/widgets/tests/test_dataframeeditor.py index 4e5f3b0ef59..ed57af8f0d8 100644 --- a/spyder/plugins/variableexplorer/widgets/tests/test_dataframeeditor.py +++ b/spyder/plugins/variableexplorer/widgets/tests/test_dataframeeditor.py @@ -358,6 +358,41 @@ def test_dataframemodel_get_bgcolor_with_missings(): 'Wrong bg color for missing of type ' + column +def test_dataframemodel_get_bgcolor_with_nullable_numbers(): + """ + Test background colors for nullable integer data types + + Regression test for spyder-ide/spyder#21222. + """ + vals = [1, 2, 3, 4, 5] + vals_na = [1, 2, 3, None, 5] + df = DataFrame({ + 'old': Series(vals), + 'old_na': Series(vals_na), + 'new': Series(vals, dtype='Int64'), + 'new_na': Series(vals_na, dtype='Int64') + }) + dfm = DataFrameModel(df) + dfm.colum_avg(0) + + # Test numbers + h0 = dataframeeditor.BACKGROUND_NUMBER_MINHUE + dh = dataframeeditor.BACKGROUND_NUMBER_HUERANGE + s = dataframeeditor.BACKGROUND_NUMBER_SATURATION + v = dataframeeditor.BACKGROUND_NUMBER_VALUE + a = dataframeeditor.BACKGROUND_NUMBER_ALPHA + for col_index in range(4): + assert colorclose(bgcolor(dfm, 0, col_index), (h0 + dh, s, v, a)) + assert colorclose(bgcolor(dfm, 3, 0), (h0 + 1 / 4 * dh, s, v, a)) + assert colorclose(bgcolor(dfm, 3, 2), (h0 + 1 / 4 * dh, s, v, a)) + + # Test null values + h, s, v, __ = QColor(dataframeeditor.BACKGROUND_NONNUMBER_COLOR).getHsvF() + alpha = dataframeeditor.BACKGROUND_MISC_ALPHA + assert colorclose(bgcolor(dfm, 3, 1), (h, s, v, alpha)) + assert colorclose(bgcolor(dfm, 3, 3), (h, s, v, alpha)) + + def test_dataframemodel_with_format_percent_d_and_nan(): """ Test DataFrameModel with format `d` and dataframe containing NaN