Skip to content

Commit 84cba2a

Browse files
Close #229 (#233)
Problem was created from creating np.nan values in all columns and then converting with pandas.
1 parent e6b608b commit 84cba2a

File tree

1 file changed

+52
-4
lines changed

1 file changed

+52
-4
lines changed

src/petab_gui/commands.py

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,45 @@
88
pd.set_option("future.no_silent_downcasting", True)
99

1010

11+
def _convert_dtype_with_nullable_int(series, dtype):
12+
"""Convert a series to the specified dtype, handling nullable integers.
13+
14+
When converting to integer types and the series contains NaN values,
15+
this function automatically uses pandas nullable integer types (Int64, Int32, etc.)
16+
instead of numpy integer types which don't support NaN.
17+
18+
Args:
19+
series: The pandas Series to convert
20+
dtype: The target dtype
21+
22+
Returns:
23+
The series with the appropriate dtype applied
24+
"""
25+
# Check if it's already a pandas nullable int type
26+
is_pandas_nullable_int = isinstance(
27+
dtype,
28+
(pd.Int64Dtype, pd.Int32Dtype, pd.Int16Dtype, pd.Int8Dtype),
29+
)
30+
31+
if is_pandas_nullable_int:
32+
# Keep pandas nullable integer types as is
33+
return series.astype(dtype)
34+
# If column has NaN and dtype is integer, use nullable Int type
35+
if np.issubdtype(dtype, np.integer) and series.isna().any():
36+
# Convert numpy int types to pandas nullable Int types
37+
if dtype == np.int64:
38+
return series.astype("Int64")
39+
if dtype == np.int32:
40+
return series.astype("Int32")
41+
if dtype == np.int16:
42+
return series.astype("Int16")
43+
if dtype == np.int8:
44+
return series.astype("Int8")
45+
# Fallback for other integer types
46+
return series.astype("Int64")
47+
return series.astype(dtype)
48+
49+
1150
class ModifyColumnCommand(QUndoCommand):
1251
"""Command to add or remove a column in the table.
1352
@@ -155,7 +194,9 @@ def redo(self):
155194
if np.any(dtypes != df.dtypes):
156195
for col, dtype in dtypes.items():
157196
if dtype != df.dtypes[col]:
158-
df[col] = df[col].astype(dtype)
197+
df[col] = _convert_dtype_with_nullable_int(
198+
df[col], dtype
199+
)
159200
self.model.endInsertRows()
160201
else:
161202
self.model.beginRemoveRows(
@@ -261,10 +302,17 @@ def _apply_changes(self, use_new: bool):
261302
for col, dtype in original_dtypes.items():
262303
if col not in update_df.columns:
263304
continue
264-
if np.issubdtype(dtype, np.number):
305+
306+
# For numeric types, convert string inputs to numbers first
307+
is_pandas_nullable_int = isinstance(
308+
dtype,
309+
(pd.Int64Dtype, pd.Int32Dtype, pd.Int16Dtype, pd.Int8Dtype),
310+
)
311+
if is_pandas_nullable_int or np.issubdtype(dtype, np.number):
265312
df[col] = pd.to_numeric(df[col], errors="coerce")
266-
else:
267-
df[col] = df[col].astype(dtype)
313+
314+
# Convert to appropriate dtype, handling nullable integers
315+
df[col] = _convert_dtype_with_nullable_int(df[col], dtype)
268316

269317
rows = [df.index.get_loc(row_key) for (row_key, _) in self.changes]
270318
cols = [

0 commit comments

Comments
 (0)