corbanyax commited on
Commit
3c93791
·
verified ·
1 Parent(s): 84c4967

Add more models and show TabFM status

Browse files
Files changed (4) hide show
  1. README.md +3 -1
  2. app.py +82 -8
  3. packages.txt +1 -0
  4. requirements.txt +1 -0
README.md CHANGED
@@ -30,7 +30,7 @@ The app includes:
30
  - A benchmark catalog with 10+ common tabular tasks, including Titanic-style survival, housing prices, fraud detection, recipe ratings, Halloween candy ranking, and classic sklearn datasets.
31
  - A leaderboard with model metrics, timing, and task-aware ranking.
32
  - Charts for accuracy/F1/AUC or RMSE/MAE/R2.
33
- - Controls for sample size, train/test split, random seed, model selection, and TabFM inclusion.
34
  - A CSV upload flow so visitors can run the same arena on their own dataset.
35
 
36
  The built-in catalog uses real open datasets where possible:
@@ -42,6 +42,8 @@ The built-in catalog uses real open datasets where possible:
42
 
43
  TabFM is loaded through the public Google Research package when available. The app keeps graceful fallbacks so the Space still works on CPU-only or dependency-constrained runtimes. Live TabFM runs are opt-in because the model weights are large and CPU-only inference can be slow.
44
 
 
 
45
  Note: TabFM's weights use their own non-commercial license. Review the upstream model license before using this Space commercially.
46
 
47
  Links:
 
30
  - A benchmark catalog with 10+ common tabular tasks, including Titanic-style survival, housing prices, fraud detection, recipe ratings, Halloween candy ranking, and classic sklearn datasets.
31
  - A leaderboard with model metrics, timing, and task-aware ranking.
32
  - Charts for accuracy/F1/AUC or RMSE/MAE/R2.
33
+ - Controls for sample size, train/test split, random seed, model selection, chart metrics, and TabFM inclusion.
34
  - A CSV upload flow so visitors can run the same arena on their own dataset.
35
 
36
  The built-in catalog uses real open datasets where possible:
 
42
 
43
  TabFM is loaded through the public Google Research package when available. The app keeps graceful fallbacks so the Space still works on CPU-only or dependency-constrained runtimes. Live TabFM runs are opt-in because the model weights are large and CPU-only inference can be slow.
44
 
45
+ Model choices include Logistic/Ridge, RandomForest, ExtraTrees, GradientBoosting, HistGradientBoosting, AdaBoost, KNN, SVM, XGBoost, LightGBM, Dummy, and optional live TabFM.
46
+
47
  Note: TabFM's weights use their own non-commercial license. Review the upstream model license before using this Space commercially.
48
 
49
  Links:
app.py CHANGED
@@ -18,7 +18,18 @@ from sklearn.base import clone
18
  from sklearn.compose import ColumnTransformer
19
  from sklearn.datasets import fetch_california_housing, fetch_openml, load_breast_cancer, load_diabetes, load_digits, load_iris, load_wine, make_classification
20
  from sklearn.dummy import DummyClassifier, DummyRegressor
21
- from sklearn.ensemble import HistGradientBoostingClassifier, HistGradientBoostingRegressor, RandomForestClassifier, RandomForestRegressor
 
 
 
 
 
 
 
 
 
 
 
22
  from sklearn.impute import SimpleImputer
23
  from sklearn.linear_model import LogisticRegression, Ridge
24
  from sklearn.metrics import (
@@ -30,8 +41,10 @@ from sklearn.metrics import (
30
  roc_auc_score,
31
  )
32
  from sklearn.model_selection import train_test_split
 
33
  from sklearn.pipeline import Pipeline
34
  from sklearn.preprocessing import OneHotEncoder, StandardScaler
 
35
 
36
 
37
  APP_TITLE = "tabBench"
@@ -716,14 +729,24 @@ def available_baselines(task: str) -> dict[str, object]:
716
  models: dict[str, object] = {
717
  "Logistic": Pipeline([("prep", make_preprocessor(pd.DataFrame(), True)), ("model", LogisticRegression(max_iter=800))]),
718
  "RandomForest": Pipeline([("prep", make_preprocessor(pd.DataFrame())), ("model", RandomForestClassifier(n_estimators=80, min_samples_leaf=2, n_jobs=-1, random_state=RANDOM_STATE))]),
 
 
719
  "HistGradientBoosting": Pipeline([("prep", make_preprocessor(pd.DataFrame())), ("model", HistGradientBoostingClassifier(max_iter=120, random_state=RANDOM_STATE))]),
 
 
 
720
  "Dummy": Pipeline([("prep", make_preprocessor(pd.DataFrame())), ("model", DummyClassifier(strategy="most_frequent"))]),
721
  }
722
  else:
723
  models = {
724
  "Ridge": Pipeline([("prep", make_preprocessor(pd.DataFrame(), True)), ("model", Ridge(alpha=1.0))]),
725
  "RandomForest": Pipeline([("prep", make_preprocessor(pd.DataFrame())), ("model", RandomForestRegressor(n_estimators=80, min_samples_leaf=2, n_jobs=-1, random_state=RANDOM_STATE))]),
 
 
726
  "HistGradientBoosting": Pipeline([("prep", make_preprocessor(pd.DataFrame())), ("model", HistGradientBoostingRegressor(max_iter=120, random_state=RANDOM_STATE))]),
 
 
 
727
  "Dummy": Pipeline([("prep", make_preprocessor(pd.DataFrame())), ("model", DummyRegressor(strategy="median"))]),
728
  }
729
  if importlib.util.find_spec("xgboost"):
@@ -743,6 +766,26 @@ def available_baselines(task: str) -> dict[str, object]:
743
  ("model", XGBRegressor(n_estimators=80, max_depth=4, learning_rate=0.08, random_state=RANDOM_STATE)),
744
  ]
745
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
746
  return models
747
 
748
 
@@ -860,6 +903,9 @@ def benchmark_frame(
860
  rows.append({"model": name, "status": "ok", "seconds": time.perf_counter() - start, **metrics})
861
  except Exception as exc:
862
  rows.append({"model": name, "status": f"failed: {exc}", "seconds": time.perf_counter() - start})
 
 
 
863
 
864
  if include_tabfm:
865
  start = time.perf_counter()
@@ -883,6 +929,8 @@ def benchmark_frame(
883
  except Exception as exc:
884
  rows.append({"model": "TabFM", "status": f"unavailable: {exc}", "seconds": time.perf_counter() - start})
885
  notes.append(f"TabFM did not run in this environment. On Spaces, keep Python 3.11 and allow the GitHub dependency plus model download for `{TABFM_MODEL_ID}`.")
 
 
886
 
887
  results = pd.DataFrame(rows)
888
  metric_cols = [c for c in ["accuracy", "f1_weighted", "roc_auc", "rmse", "mae", "r2", "rank_score", "seconds"] if c in results.columns]
@@ -913,6 +961,9 @@ def metric_chart(results: pd.DataFrame, selected_metrics: list[str] | None = Non
913
  return go.Figure()
914
 
915
  clean = results.sort_values("rank") if "rank" in results.columns else results.copy()
 
 
 
916
  x_labels = clean["model"].astype(str).tolist()
917
  if chart_style == "Radar":
918
  normalized = clean[["model", *metric_cols]].copy()
@@ -985,6 +1036,7 @@ def metric_chart(results: pd.DataFrame, selected_metrics: list[str] | None = Non
985
  def bar_chart(results: pd.DataFrame, selected_metrics: list[str] | None = None) -> go.Figure:
986
  if results is None or results.empty:
987
  return go.Figure()
 
988
  selected_metrics = selected_metrics or METRIC_CHOICES
989
  metric_cols = [c for c in selected_metrics if c in results.columns and results[c].notna().any()]
990
  if not metric_cols:
@@ -992,6 +1044,9 @@ def bar_chart(results: pd.DataFrame, selected_metrics: list[str] | None = None)
992
  if not metric_cols:
993
  return go.Figure()
994
  clean = results.sort_values("rank") if "rank" in results.columns else results.copy()
 
 
 
995
  long = clean.melt(id_vars=["model"], value_vars=metric_cols, var_name="metric", value_name="score")
996
  fig = px.bar(
997
  long,
@@ -1015,6 +1070,11 @@ def bar_chart(results: pd.DataFrame, selected_metrics: list[str] | None = None)
1015
  def time_chart(results: pd.DataFrame) -> go.Figure:
1016
  if results is None or results.empty or "seconds" not in results:
1017
  return go.Figure()
 
 
 
 
 
1018
  fig = px.scatter(
1019
  results,
1020
  x="seconds",
@@ -1111,7 +1171,21 @@ def catalog_table() -> pd.DataFrame:
1111
  )
1112
 
1113
 
1114
- DEFAULT_MODELS = ["Logistic", "Ridge", "RandomForest", "HistGradientBoosting", "XGBoost", "Dummy"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1115
 
1116
 
1117
  def build_app() -> gr.Blocks:
@@ -1151,8 +1225,8 @@ def build_app() -> gr.Blocks:
1151
  sample = gr.Slider(100, 50000, value=1000, step=100, label="Sample size")
1152
  test_pct = gr.Slider(10, 40, value=25, step=5, label="Test split (%)")
1153
  seed = gr.Number(value=42, precision=0, label="Random seed")
1154
- models = gr.CheckboxGroup(DEFAULT_MODELS, value=["HistGradientBoosting", "Dummy"], label="Baselines")
1155
- include_tabfm = gr.Checkbox(value=False, label="Run TabFM live")
1156
  metric_toggles = gr.CheckboxGroup(METRIC_CHOICES, value=["rmse", "mae", "r2", "accuracy", "f1_weighted", "roc_auc"], label="Chart metrics")
1157
  chart_style = gr.Radio(["Line", "Radar"], value="Line", label="Chart style")
1158
  with gr.Accordion("TabFM tuning", open=False):
@@ -1169,10 +1243,10 @@ def build_app() -> gr.Blocks:
1169
  with gr.Column(scale=3):
1170
  summary = gr.Markdown()
1171
  leaderboard = gr.Dataframe(label="Leaderboard", interactive=False)
 
1172
  with gr.Row():
1173
  chart = gr.Plot(label="Metric comparison")
1174
  speed = gr.Plot(label="Speed")
1175
- bars = gr.Plot(label="Grouped comparison")
1176
  preview = gr.Dataframe(label="Held-out preview", interactive=False)
1177
  run_inputs = [
1178
  dataset,
@@ -1208,8 +1282,8 @@ def build_app() -> gr.Blocks:
1208
  upload_sample = gr.Slider(100, 50000, value=1000, step=100, label="Sample size")
1209
  upload_test_pct = gr.Slider(10, 40, value=25, step=5, label="Test split (%)")
1210
  upload_seed = gr.Number(value=42, precision=0, label="Random seed")
1211
- upload_models = gr.CheckboxGroup(DEFAULT_MODELS, value=["HistGradientBoosting", "Dummy"], label="Baselines")
1212
- upload_tabfm = gr.Checkbox(value=False, label="Run TabFM live")
1213
  upload_metric_toggles = gr.CheckboxGroup(METRIC_CHOICES, value=["rmse", "mae", "r2", "accuracy", "f1_weighted", "roc_auc"], label="Chart metrics")
1214
  upload_chart_style = gr.Radio(["Line", "Radar"], value="Line", label="Chart style")
1215
  with gr.Accordion("TabFM tuning", open=False):
@@ -1226,10 +1300,10 @@ def build_app() -> gr.Blocks:
1226
  with gr.Column(scale=3):
1227
  upload_summary = gr.Markdown()
1228
  upload_leaderboard = gr.Dataframe(label="Upload leaderboard", interactive=False)
 
1229
  with gr.Row():
1230
  upload_chart = gr.Plot(label="Metric comparison")
1231
  upload_speed = gr.Plot(label="Speed")
1232
- upload_bars = gr.Plot(label="Grouped comparison")
1233
  upload_preview = gr.Dataframe(label="Held-out preview", interactive=False)
1234
  upload_btn.click(
1235
  run_upload,
 
18
  from sklearn.compose import ColumnTransformer
19
  from sklearn.datasets import fetch_california_housing, fetch_openml, load_breast_cancer, load_diabetes, load_digits, load_iris, load_wine, make_classification
20
  from sklearn.dummy import DummyClassifier, DummyRegressor
21
+ from sklearn.ensemble import (
22
+ AdaBoostClassifier,
23
+ AdaBoostRegressor,
24
+ ExtraTreesClassifier,
25
+ ExtraTreesRegressor,
26
+ GradientBoostingClassifier,
27
+ GradientBoostingRegressor,
28
+ HistGradientBoostingClassifier,
29
+ HistGradientBoostingRegressor,
30
+ RandomForestClassifier,
31
+ RandomForestRegressor,
32
+ )
33
  from sklearn.impute import SimpleImputer
34
  from sklearn.linear_model import LogisticRegression, Ridge
35
  from sklearn.metrics import (
 
41
  roc_auc_score,
42
  )
43
  from sklearn.model_selection import train_test_split
44
+ from sklearn.neighbors import KNeighborsClassifier, KNeighborsRegressor
45
  from sklearn.pipeline import Pipeline
46
  from sklearn.preprocessing import OneHotEncoder, StandardScaler
47
+ from sklearn.svm import SVC, SVR
48
 
49
 
50
  APP_TITLE = "tabBench"
 
729
  models: dict[str, object] = {
730
  "Logistic": Pipeline([("prep", make_preprocessor(pd.DataFrame(), True)), ("model", LogisticRegression(max_iter=800))]),
731
  "RandomForest": Pipeline([("prep", make_preprocessor(pd.DataFrame())), ("model", RandomForestClassifier(n_estimators=80, min_samples_leaf=2, n_jobs=-1, random_state=RANDOM_STATE))]),
732
+ "ExtraTrees": Pipeline([("prep", make_preprocessor(pd.DataFrame())), ("model", ExtraTreesClassifier(n_estimators=120, min_samples_leaf=2, n_jobs=-1, random_state=RANDOM_STATE))]),
733
+ "GradientBoosting": Pipeline([("prep", make_preprocessor(pd.DataFrame())), ("model", GradientBoostingClassifier(n_estimators=100, learning_rate=0.06, random_state=RANDOM_STATE))]),
734
  "HistGradientBoosting": Pipeline([("prep", make_preprocessor(pd.DataFrame())), ("model", HistGradientBoostingClassifier(max_iter=120, random_state=RANDOM_STATE))]),
735
+ "AdaBoost": Pipeline([("prep", make_preprocessor(pd.DataFrame())), ("model", AdaBoostClassifier(n_estimators=80, learning_rate=0.08, random_state=RANDOM_STATE))]),
736
+ "KNN": Pipeline([("prep", make_preprocessor(pd.DataFrame(), True)), ("model", KNeighborsClassifier(n_neighbors=7))]),
737
+ "SVM": Pipeline([("prep", make_preprocessor(pd.DataFrame(), True)), ("model", SVC(C=1.0, probability=True, random_state=RANDOM_STATE))]),
738
  "Dummy": Pipeline([("prep", make_preprocessor(pd.DataFrame())), ("model", DummyClassifier(strategy="most_frequent"))]),
739
  }
740
  else:
741
  models = {
742
  "Ridge": Pipeline([("prep", make_preprocessor(pd.DataFrame(), True)), ("model", Ridge(alpha=1.0))]),
743
  "RandomForest": Pipeline([("prep", make_preprocessor(pd.DataFrame())), ("model", RandomForestRegressor(n_estimators=80, min_samples_leaf=2, n_jobs=-1, random_state=RANDOM_STATE))]),
744
+ "ExtraTrees": Pipeline([("prep", make_preprocessor(pd.DataFrame())), ("model", ExtraTreesRegressor(n_estimators=120, min_samples_leaf=2, n_jobs=-1, random_state=RANDOM_STATE))]),
745
+ "GradientBoosting": Pipeline([("prep", make_preprocessor(pd.DataFrame())), ("model", GradientBoostingRegressor(n_estimators=100, learning_rate=0.06, random_state=RANDOM_STATE))]),
746
  "HistGradientBoosting": Pipeline([("prep", make_preprocessor(pd.DataFrame())), ("model", HistGradientBoostingRegressor(max_iter=120, random_state=RANDOM_STATE))]),
747
+ "AdaBoost": Pipeline([("prep", make_preprocessor(pd.DataFrame())), ("model", AdaBoostRegressor(n_estimators=80, learning_rate=0.08, random_state=RANDOM_STATE))]),
748
+ "KNN": Pipeline([("prep", make_preprocessor(pd.DataFrame(), True)), ("model", KNeighborsRegressor(n_neighbors=7))]),
749
+ "SVM": Pipeline([("prep", make_preprocessor(pd.DataFrame(), True)), ("model", SVR(C=1.0))]),
750
  "Dummy": Pipeline([("prep", make_preprocessor(pd.DataFrame())), ("model", DummyRegressor(strategy="median"))]),
751
  }
752
  if importlib.util.find_spec("xgboost"):
 
766
  ("model", XGBRegressor(n_estimators=80, max_depth=4, learning_rate=0.08, random_state=RANDOM_STATE)),
767
  ]
768
  )
769
+ if importlib.util.find_spec("lightgbm"):
770
+ try:
771
+ from lightgbm import LGBMClassifier, LGBMRegressor
772
+
773
+ if task == "classification":
774
+ models["LightGBM"] = Pipeline(
775
+ [
776
+ ("prep", make_preprocessor(pd.DataFrame())),
777
+ ("model", LGBMClassifier(n_estimators=120, learning_rate=0.06, random_state=RANDOM_STATE, verbose=-1)),
778
+ ]
779
+ )
780
+ else:
781
+ models["LightGBM"] = Pipeline(
782
+ [
783
+ ("prep", make_preprocessor(pd.DataFrame())),
784
+ ("model", LGBMRegressor(n_estimators=120, learning_rate=0.06, random_state=RANDOM_STATE, verbose=-1)),
785
+ ]
786
+ )
787
+ except Exception:
788
+ pass
789
  return models
790
 
791
 
 
903
  rows.append({"model": name, "status": "ok", "seconds": time.perf_counter() - start, **metrics})
904
  except Exception as exc:
905
  rows.append({"model": name, "status": f"failed: {exc}", "seconds": time.perf_counter() - start})
906
+ for name in selected_models:
907
+ if name not in models:
908
+ rows.append({"model": name, "status": "unavailable in this runtime", "seconds": 0.0, "rank_score": np.nan})
909
 
910
  if include_tabfm:
911
  start = time.perf_counter()
 
929
  except Exception as exc:
930
  rows.append({"model": "TabFM", "status": f"unavailable: {exc}", "seconds": time.perf_counter() - start})
931
  notes.append(f"TabFM did not run in this environment. On Spaces, keep Python 3.11 and allow the GitHub dependency plus model download for `{TABFM_MODEL_ID}`.")
932
+ else:
933
+ rows.append({"model": "TabFM", "status": "skipped - enable Run TabFM live", "seconds": 0.0, "rank_score": np.nan})
934
 
935
  results = pd.DataFrame(rows)
936
  metric_cols = [c for c in ["accuracy", "f1_weighted", "roc_auc", "rmse", "mae", "r2", "rank_score", "seconds"] if c in results.columns]
 
961
  return go.Figure()
962
 
963
  clean = results.sort_values("rank") if "rank" in results.columns else results.copy()
964
+ clean = clean.dropna(subset=metric_cols, how="all")
965
+ if clean.empty:
966
+ return go.Figure()
967
  x_labels = clean["model"].astype(str).tolist()
968
  if chart_style == "Radar":
969
  normalized = clean[["model", *metric_cols]].copy()
 
1036
  def bar_chart(results: pd.DataFrame, selected_metrics: list[str] | None = None) -> go.Figure:
1037
  if results is None or results.empty:
1038
  return go.Figure()
1039
+ results = results.copy()
1040
  selected_metrics = selected_metrics or METRIC_CHOICES
1041
  metric_cols = [c for c in selected_metrics if c in results.columns and results[c].notna().any()]
1042
  if not metric_cols:
 
1044
  if not metric_cols:
1045
  return go.Figure()
1046
  clean = results.sort_values("rank") if "rank" in results.columns else results.copy()
1047
+ clean = clean.dropna(subset=metric_cols, how="all")
1048
+ if clean.empty:
1049
+ return go.Figure()
1050
  long = clean.melt(id_vars=["model"], value_vars=metric_cols, var_name="metric", value_name="score")
1051
  fig = px.bar(
1052
  long,
 
1070
  def time_chart(results: pd.DataFrame) -> go.Figure:
1071
  if results is None or results.empty or "seconds" not in results:
1072
  return go.Figure()
1073
+ results = results.copy()
1074
+ if "status" in results.columns:
1075
+ results = results[~results["status"].astype(str).str.startswith("skipped")]
1076
+ if results.empty:
1077
+ return go.Figure()
1078
  fig = px.scatter(
1079
  results,
1080
  x="seconds",
 
1171
  )
1172
 
1173
 
1174
+ DEFAULT_MODELS = [
1175
+ "Logistic",
1176
+ "Ridge",
1177
+ "RandomForest",
1178
+ "ExtraTrees",
1179
+ "GradientBoosting",
1180
+ "HistGradientBoosting",
1181
+ "AdaBoost",
1182
+ "KNN",
1183
+ "SVM",
1184
+ "XGBoost",
1185
+ "LightGBM",
1186
+ "Dummy",
1187
+ ]
1188
+ DEFAULT_SELECTED_MODELS = ["HistGradientBoosting", "XGBoost", "LightGBM", "Dummy"]
1189
 
1190
 
1191
  def build_app() -> gr.Blocks:
 
1225
  sample = gr.Slider(100, 50000, value=1000, step=100, label="Sample size")
1226
  test_pct = gr.Slider(10, 40, value=25, step=5, label="Test split (%)")
1227
  seed = gr.Number(value=42, precision=0, label="Random seed")
1228
+ models = gr.CheckboxGroup(DEFAULT_MODELS, value=DEFAULT_SELECTED_MODELS, label="Baselines")
1229
+ include_tabfm = gr.Checkbox(value=False, label="Run TabFM live (adds TabFM row)")
1230
  metric_toggles = gr.CheckboxGroup(METRIC_CHOICES, value=["rmse", "mae", "r2", "accuracy", "f1_weighted", "roc_auc"], label="Chart metrics")
1231
  chart_style = gr.Radio(["Line", "Radar"], value="Line", label="Chart style")
1232
  with gr.Accordion("TabFM tuning", open=False):
 
1243
  with gr.Column(scale=3):
1244
  summary = gr.Markdown()
1245
  leaderboard = gr.Dataframe(label="Leaderboard", interactive=False)
1246
+ bars = gr.Plot(label="Main grouped comparison")
1247
  with gr.Row():
1248
  chart = gr.Plot(label="Metric comparison")
1249
  speed = gr.Plot(label="Speed")
 
1250
  preview = gr.Dataframe(label="Held-out preview", interactive=False)
1251
  run_inputs = [
1252
  dataset,
 
1282
  upload_sample = gr.Slider(100, 50000, value=1000, step=100, label="Sample size")
1283
  upload_test_pct = gr.Slider(10, 40, value=25, step=5, label="Test split (%)")
1284
  upload_seed = gr.Number(value=42, precision=0, label="Random seed")
1285
+ upload_models = gr.CheckboxGroup(DEFAULT_MODELS, value=DEFAULT_SELECTED_MODELS, label="Baselines")
1286
+ upload_tabfm = gr.Checkbox(value=False, label="Run TabFM live (adds TabFM row)")
1287
  upload_metric_toggles = gr.CheckboxGroup(METRIC_CHOICES, value=["rmse", "mae", "r2", "accuracy", "f1_weighted", "roc_auc"], label="Chart metrics")
1288
  upload_chart_style = gr.Radio(["Line", "Radar"], value="Line", label="Chart style")
1289
  with gr.Accordion("TabFM tuning", open=False):
 
1300
  with gr.Column(scale=3):
1301
  upload_summary = gr.Markdown()
1302
  upload_leaderboard = gr.Dataframe(label="Upload leaderboard", interactive=False)
1303
+ upload_bars = gr.Plot(label="Main grouped comparison")
1304
  with gr.Row():
1305
  upload_chart = gr.Plot(label="Metric comparison")
1306
  upload_speed = gr.Plot(label="Speed")
 
1307
  upload_preview = gr.Dataframe(label="Held-out preview", interactive=False)
1308
  upload_btn.click(
1309
  run_upload,
packages.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ libgomp1
requirements.txt CHANGED
@@ -5,5 +5,6 @@ scikit-learn>=1.5.0,<1.9
5
  plotly>=5.22.0,<7
6
  scipy>=1.13.0,<1.18
7
  xgboost>=2.1.0,<3
 
8
  kagglehub>=0.3.6,<1
9
  tabfm[pytorch] @ git+https://github.com/google-research/tabfm.git
 
5
  plotly>=5.22.0,<7
6
  scipy>=1.13.0,<1.18
7
  xgboost>=2.1.0,<3
8
+ lightgbm>=4.5.0,<5
9
  kagglehub>=0.3.6,<1
10
  tabfm[pytorch] @ git+https://github.com/google-research/tabfm.git