Describe the bug
Three ordinary tabular inputs crash fit() under the default preprocessing. All reproduced
independently.
A DataFrame with only categorical columns crashes MLP / ResNet / NODE with a Long-vs-Float dtype error
Where: deeptab/architectures/mlp.py (131 (same pattern at resnet.py:118, node.py:112, ndtf.py:116, mambatab.py:102))
Non-embedding architectures build their input with torch.cat([t for tensors in data for t in tensors], dim=1). Ordinal-encoded categorical features arrive as torch.long and numerical ones as torch.float32; torch's type promotion normally lifts the result to float, but when the dataset has zero numerical columns every tensor is long, so the concatenation stays long and the first nn.Linear raises.
Observed: RuntimeError: mat1 and mat2 must have the same dtype, but got Long and Float from torch/nn/modules/linear.py during the first forward pass. Reproduced identically for MLPRegressor, MLPClassifier, ResNetRegressor; NODERegressor gives RuntimeError: expected m1 and m2 to have the same dtype, but got: long long != float at deeptab/nn/blocks/node.py:370. The same call with a single categorical column (X[['c']]) fails the same way. FTTransformer, TabM, AutoInt and SAINT (embedding-based) handle it fine.
Expected: An all-categorical table is ordinary tabular input; these models should either cast the concatenated tensor to float (e.g. .float() after torch.cat) or raise an actionable error naming the unsupported configuration. Distinct from #426: that issue is TabTransformer raising torch.cat(): expected a non-empty list of Tensors at tabtransformer.py:135 — a different file, line and exception.
Repro
import warnings; warnings.simplefilter('ignore')
import numpy as np, pandas as pd
from deeptab.models import MLPRegressor, ResNetRegressor, NODERegressor, MLPClassifier
Q = dict(accelerator='cpu', devices=1, enable_progress_bar=False, enable_model_summary=False, logger=False)
rng = np.random.default_rng(0); n = 40
X = pd.DataFrame({'c': rng.choice(list('xyz'), size=n), 'd': rng.choice(list('pq'), size=n)})
y = rng.normal(size=n)
MLPRegressor().fit(X, y, max_epochs=1, batch_size=16, patience=1, **Q)
A zero-variance (all-constant) numeric column crashes fit() with KeyError: 0 under the default preprocessing
Where: deeptab/data/datamodule.py (179 (self.preprocessor.fit(...); root cause in pretab's PLE tree_to_code))
DeepTab's default numerical preprocessing is PLE ('imputer -> minmax -> ple'), which fits a decision tree per numeric column. A constant column (or a constant target, or a tiny n) produces a degenerate single-node tree and pretab's tree_to_code raises a bare KeyError: 0. Because the tree is fitted on the training split only, a near-constant column makes the crash depend on the random split, so it looks intermittent.
Observed: KeyError: 0 from pretab/transformers/ple/tree_to_code.py:52. The near-constant variant gives [(0,'ok'),(1,'ok'),(2,'ok'),(3,'ok'),(4,'KeyError')] — a crash for 1 of 5 seeds. The same KeyError also fires for a constant target (y = np.full(n, 3.0)) and for n=2 rows. Setting PreprocessingConfig(numerical_preprocessing='standardization') makes all of these work, confirming PLE is the trigger.
Expected: A constant column is common in real tables and should be handled (dropped, or passed through as a single constant bin), or rejected with a DeepTab-level DataError naming the column. A bare KeyError: 0 with a stack trace ending inside a third-party package gives the user nothing to act on. The fix likely belongs upstream in pretab, but DeepTab chooses PLE as its default and is the only surface the user sees.
Repro
import warnings; warnings.simplefilter('ignore')
import numpy as np, pandas as pd
from deeptab.models import MLPRegressor
Q = dict(accelerator='cpu', devices=1, enable_progress_bar=False, enable_model_summary=False, logger=False)
rng = np.random.default_rng(0); n = 40
X = pd.DataFrame({'a': rng.normal(size=n), 'b': rng.normal(size=n)}); y = rng.normal(size=n)
X['const'] = 3.0 # a flag that is always 1, an all-zero counter, etc.
MLPRegressor().fit(X, y, max_epochs=1, batch_size=16, patience=1, **Q) # KeyError: 0
# split-dependent variant: one differing row out of 40
X2 = pd.DataFrame({'a': rng.normal(size=n), 'flag': np.ones(n)}); X2.loc[7, 'flag'] = 0.0
for rs in range(5):
try:
MLPRegressor().fit(X2, y, random_state=rs, max_epochs=1, batch_size=16, patience=1, **Q); print(rs, 'ok')
except Exception as e: print(rs, type(e).__name__)
An entirely-NaN column is warned about as "will be imputed with a constant", then makes fit() crash
Where: deeptab/core/sklearn_compat.py (82-88)
ensure_dataframe detects all-NaN columns and emits a DataWarning promising they "will be imputed with a constant". That imputation never happens: sklearn's SimpleImputer drops the all-NaN column, leaving the downstream scaler with zero features, and fit() dies with an opaque sklearn ValueError.
Observed: Warning emitted: "The following column(s) are entirely NaN and will be imputed with a constant: ['nanc']. Consider dropping them before calling fit()."
Then: ValueError: Found array with 0 feature(s) (shape=(32, 0)) while a minimum of 1 is required by MinMaxScaler. Reproduced with both the default preprocessing and PreprocessingConfig(numerical_preprocessing='standardization').
Expected: Either the promised constant imputation actually happens (fit succeeds, the column contributes a constant feature), or the all-NaN column is dropped and the warning says so, or DeepTab raises its own clear DataError naming the offending column. Warning text that describes behaviour the library does not implement is worse than no warning.
Repro
import warnings; warnings.simplefilter('ignore')
import numpy as np, pandas as pd
from deeptab.models import MLPRegressor
Q = dict(accelerator='cpu', devices=1, enable_progress_bar=False, enable_model_summary=False, logger=False)
rng = np.random.default_rng(0); n = 40
X = pd.DataFrame({'a': rng.normal(size=n), 'b': rng.normal(size=n)})
X['nanc'] = np.nan
y = rng.normal(size=n)
MLPRegressor().fit(X, y, max_epochs=1, batch_size=16, patience=1, **Q)
Expected behavior
An all-categorical frame is a completely normal tabular dataset (and the headline use case for
TabTransformer-style models). A constant column and an all-NaN column are routine in real data and
should be handled or rejected with an actionable message — the all-NaN case is especially poor because
the library explicitly warns that it "will be imputed with a constant" and then crashes anyway.
Screenshots
n/a
Desktop (please complete the following information):
- OS: macOS (Darwin 25.5.0, arm64)
- Python version: 3.11.15
- deeptab Version: 2.0.0 (main @ 4e6a359)
Additional context
torch 2.9.1, lightning 2.6.5, scikit-learn 1.9.0, numpy 2.4.6. Found in a second-pass review of v2.0.0
(seven independent lenses, each finding adversarially re-verified by a second reviewer, then re-run by
hand). Distinct from the already-filed #409-#426.
Describe the bug
Three ordinary tabular inputs crash
fit()under the default preprocessing. All reproducedindependently.
A DataFrame with only categorical columns crashes MLP / ResNet / NODE with a Long-vs-Float dtype error
Where:
deeptab/architectures/mlp.py(131 (same pattern at resnet.py:118, node.py:112, ndtf.py:116, mambatab.py:102))Non-embedding architectures build their input with
torch.cat([t for tensors in data for t in tensors], dim=1). Ordinal-encoded categorical features arrive as torch.long and numerical ones as torch.float32; torch's type promotion normally lifts the result to float, but when the dataset has zero numerical columns every tensor is long, so the concatenation stays long and the first nn.Linear raises.Observed:
RuntimeError: mat1 and mat2 must have the same dtype, but got Long and Floatfrom torch/nn/modules/linear.py during the first forward pass. Reproduced identically for MLPRegressor, MLPClassifier, ResNetRegressor; NODERegressor givesRuntimeError: expected m1 and m2 to have the same dtype, but got: long long != floatat deeptab/nn/blocks/node.py:370. The same call with a single categorical column (X[['c']]) fails the same way. FTTransformer, TabM, AutoInt and SAINT (embedding-based) handle it fine.Expected: An all-categorical table is ordinary tabular input; these models should either cast the concatenated tensor to float (e.g.
.float()after torch.cat) or raise an actionable error naming the unsupported configuration. Distinct from #426: that issue is TabTransformer raisingtorch.cat(): expected a non-empty list of Tensorsat tabtransformer.py:135 — a different file, line and exception.Repro
A zero-variance (all-constant) numeric column crashes fit() with
KeyError: 0under the default preprocessingWhere:
deeptab/data/datamodule.py(179 (self.preprocessor.fit(...); root cause in pretab's PLE tree_to_code))DeepTab's default numerical preprocessing is PLE ('imputer -> minmax -> ple'), which fits a decision tree per numeric column. A constant column (or a constant target, or a tiny n) produces a degenerate single-node tree and pretab's tree_to_code raises a bare
KeyError: 0. Because the tree is fitted on the training split only, a near-constant column makes the crash depend on the random split, so it looks intermittent.Observed:
KeyError: 0from pretab/transformers/ple/tree_to_code.py:52. The near-constant variant gives [(0,'ok'),(1,'ok'),(2,'ok'),(3,'ok'),(4,'KeyError')] — a crash for 1 of 5 seeds. The same KeyError also fires for a constant target (y = np.full(n, 3.0)) and for n=2 rows. Setting PreprocessingConfig(numerical_preprocessing='standardization') makes all of these work, confirming PLE is the trigger.Expected: A constant column is common in real tables and should be handled (dropped, or passed through as a single constant bin), or rejected with a DeepTab-level DataError naming the column. A bare
KeyError: 0with a stack trace ending inside a third-party package gives the user nothing to act on. The fix likely belongs upstream in pretab, but DeepTab chooses PLE as its default and is the only surface the user sees.Repro
An entirely-NaN column is warned about as "will be imputed with a constant", then makes fit() crash
Where:
deeptab/core/sklearn_compat.py(82-88)ensure_dataframe detects all-NaN columns and emits a DataWarning promising they "will be imputed with a constant". That imputation never happens: sklearn's SimpleImputer drops the all-NaN column, leaving the downstream scaler with zero features, and fit() dies with an opaque sklearn ValueError.
Observed: Warning emitted: "The following column(s) are entirely NaN and will be imputed with a constant: ['nanc']. Consider dropping them before calling fit()."
Then:
ValueError: Found array with 0 feature(s) (shape=(32, 0)) while a minimum of 1 is required by MinMaxScaler.Reproduced with both the default preprocessing and PreprocessingConfig(numerical_preprocessing='standardization').Expected: Either the promised constant imputation actually happens (fit succeeds, the column contributes a constant feature), or the all-NaN column is dropped and the warning says so, or DeepTab raises its own clear DataError naming the offending column. Warning text that describes behaviour the library does not implement is worse than no warning.
Repro
Expected behavior
An all-categorical frame is a completely normal tabular dataset (and the headline use case for
TabTransformer-style models). A constant column and an all-NaN column are routine in real data and
should be handled or rejected with an actionable message — the all-NaN case is especially poor because
the library explicitly warns that it "will be imputed with a constant" and then crashes anyway.
Screenshots
n/a
Desktop (please complete the following information):
Additional context
torch 2.9.1, lightning 2.6.5, scikit-learn 1.9.0, numpy 2.4.6. Found in a second-pass review of v2.0.0
(seven independent lenses, each finding adversarially re-verified by a second reviewer, then re-run by
hand). Distinct from the already-filed #409-#426.