from __future__ import annotations from pathlib import Path import gradio as gr import pandas as pd from apscheduler.schedulers.background import BackgroundScheduler from constants import Constants, model_type_emoji from gradio_leaderboard import ColumnFilter, Leaderboard, SelectColumns TITLE = """

TabArena Leaderboard for Predictive Machine Learning on IID Tabular Data

""" INTRODUCTION_TEXT = """ TabArena is a living benchmark system for predictive machine learning on tabular data. The goal of TabArena and its leaderboard is to asses the peak performance of model-specific pipelines. **Datasets:** Currently, the leaderboard is based on a manually curated collection of 51 tabular classification and regression datasets for independent and identically distributed (IID) data, spanning the small to medium data regime. The datasets were carefully curated to represent various real-world predictive machine learning use cases. **Models:** The focus of the leaderboard is on model-specific pipelines. Each pipeline is evaluated with default or tuned hyperparameter configuration or as an ensemble of tuned configurations. Each model is implemented in a tested real-world pipeline that was optimized to get the most out of the model by the maintainers of TabArena, and where possible together with the authors of the model. **Metrics:** The leaderboard is ranked based on Elo. We present several additional metrics. See the `About` tab for more information on the metrics. **Reference Pipeline:** The leaderboard includes a reference pipeline, which is applied independently of the tuning protocol and constraints we constructed for models within TabArena. The reference pipeline aims to represent the performance quickly achievable by a practitioner on a dataset. The current reference pipeline is the predictive machine learning system AutoGluon (version 1.3, with the best_quality preset and 4 hours for training). AutoGluon represents an ensemble pipeline across various model types and thus provides a reference for model-specific pipelines. The current leaderboard is based on TabArena-v0.1. """ ABOUT_TEXT = """ TabArena is a living benchmark system for predictive machine learning on tabular data. We introduce TabArena and provide an overview of TabArena-v0.1 in our paper: TBA. ## Using TabArena for Benchmarking To compare your own methods to the pre-computed results for all models on the leaderboard, you can use the TabArena framework. For examples on how to use TabArena for benchmarking, please see https://github.com/TabArena/tabarena_benchmarking_examples ## Contributing to the Leaderboard; Contributing Models For guidelines on how to contribute your model to TabArena, or the result of your model to the official leaderboard, please see the appendix of our paper: TBA. ## Contributing Data For anything related to the datasets used in TabArena, please see https://github.com/TabArena/tabarena_dataset_curation --- ## Leaderboard Documentation The leaderboard is ranked by Elo and includes several other metrics. Here is a short description for these metrics: #### Elo We evaluate models using the Elo rating system, following Chatbot Arena. Elo is a pairwise comparison-based rating system where each model's rating predicts its expected win probability against others, with a 400-point Elo gap corresponding to a 10 to 1 (91\%) expected win rate. We calibrate 1000 Elo to the performance of our default random forest configuration across all figures, and perform 100 rounds of bootstrapping to obtain 95\% confidence intervals. Elo scores are computed using ROC AUC for binary classification, log-loss for multiclass classification, and RMSE for regression. #### Normalized Score Following TabRepo, we linearly rescale the error such that the best method has a normalized score of one, and the median method has a normalized score of 0. Scores below zero are clipped to zero. These scores are then averaged across datasets. #### Average Rank Ranks of methods are computed on each dataset (lower is better) and averaged. #### Harmonic Mean Rank Taking the harmonic mean of ranks, 1/((1/N) * sum(1/rank_i for i in range(N))), more strongly favors methods having very low ranks on some datasets. It therefore favors methods that are sometimes very good and sometimes very bad over methods that are always mediocre, as the former are more likely to be useful in conjunction with other methods. #### Improvability We introduce improvability as a metric that measures how many percent lower the error of the best method is than the current method on a dataset. This is then averaged over datasets. Formally, for a single dataset improvability is (err_i - besterr_i)/err_i * 100\%. Improvability is always between $0\%$ and $100\%$. --- ## Contact For most inquires, please open issues in the relevant GitHub repository or here on HuggingFace. For any other inquiries related to TabArena, please reach out to: contact@tabarena.ai ### Core Maintainers The current core maintainers of TabArena are: [Nick Erickson](https://github.com/Innixma), [Lennart Purucker](https://github.com/LennartPurucker/), [Andrej Tschalzev](https://github.com/atschalz), [David Holzmüller](https://github.com/dholzmueller) """ CITATION_BUTTON_LABEL = ( "If you use TabArena or the leaderboard in your research please cite the following:" ) CITATION_BUTTON_TEXT = r""" @article{ TBA, } """ def get_model_family(model_name: str) -> str: prefixes_mapping = { Constants.reference: ["AutoGluon"], Constants.neural_network: ["REALMLP", "TabM", "FASTAI", "MNCA", "NN_TORCH"], Constants.tree: ["GBM", "CAT", "EBM", "XGB", "XT", "RF"], Constants.foundational: ["TABDPT", "TABICL", "TABPFN"], Constants.baseline: ["KNN", "LR"], } for method_type, prefixes in prefixes_mapping.items(): for prefix in prefixes: if prefix.lower() in model_name.lower(): return method_type return Constants.other def rename_map(model_name: str) -> str: rename_map = { "TABM": "TabM", "REALMLP": "RealMLP", "GBM": "LightGBM", "CAT": "CatBoost", "XGB": "XGBoost", "XT": "ExtraTrees", "RF": "RandomForest", "MNCA": "ModernNCA", "NN_TORCH": "TorchMLP", "FASTAI": "FastaiMLP", "TABPFNV2": "TabPFNv2", "EBM": "EBM", "TABDPT": "TabDPT", "TABICL": "TabICL", "KNN": "KNN", "LR": "Linear", } for prefix in rename_map: if prefix in model_name: return model_name.replace(prefix, rename_map[prefix]) return model_name def load_data(filename: str): df_leaderboard = pd.read_csv(Path(__file__).parent / "data" / f"{filename}.csv.zip") print( f"Loaded dataframe with {len(df_leaderboard)} rows and columns {df_leaderboard.columns}" ) # add model family information df_leaderboard["Type"] = df_leaderboard.loc[:, "method"].apply( lambda s: model_type_emoji[get_model_family(s)] ) df_leaderboard["TypeName"] = df_leaderboard.loc[:, "method"].apply( lambda s: get_model_family(s) ) df_leaderboard["method"] = df_leaderboard["method"].apply(rename_map) # elo,elo+,elo-,mrr df_leaderboard["Elo 95% CI"] = ( "+" + df_leaderboard["elo+"].round(0).astype(int).astype(str) + "/-" + df_leaderboard["elo-"].round(0).astype(int).astype(str) ) # select only the columns we want to display df_leaderboard["normalized-score"] = 1 - df_leaderboard["normalized-error"] df_leaderboard["hmr"] = 1/df_leaderboard["mrr"] df_leaderboard["improvability"] = 100 * df_leaderboard["champ_delta"] df_leaderboard = df_leaderboard.loc[ :, [ "Type", "TypeName", "method", "elo", "Elo 95% CI", "normalized-score", "rank", "hmr", "improvability", "median_time_train_s_per_1K", "median_time_infer_s_per_1K", ], ] # round for better display df_leaderboard[["elo", "Elo 95% CI"]] = df_leaderboard[["elo", "Elo 95% CI"]].round( 0 ) df_leaderboard[["median_time_train_s_per_1K", "rank", "hmr"]] = df_leaderboard[ ["median_time_train_s_per_1K", "rank", "hmr"] ].round(2) df_leaderboard[["normalized-score", "median_time_infer_s_per_1K", "improvability"]] = df_leaderboard[ ["normalized-score", "median_time_infer_s_per_1K", "improvability"] ].round(3) df_leaderboard = df_leaderboard.sort_values(by="elo", ascending=False) df_leaderboard = df_leaderboard.reset_index(drop=True) df_leaderboard = df_leaderboard.reset_index(names="#") # rename some columns return df_leaderboard.rename( columns={ "median_time_train_s_per_1K": "Median Train Time (s/1K) [⬇️]", "median_time_infer_s_per_1K": "Median Predict Time (s/1K) [⬇️]", "method": "Model", "elo": "Elo [⬆️]", "rank": "Rank [⬇️]", "normalized-score": "Normalized Score [⬆️]", "hmr": "Harmonic Mean Rank [⬇️]", "improvability": "Improvability (%) [⬇️]", } ) def make_leaderboard(df_leaderboard: pd.DataFrame) -> Leaderboard: df_leaderboard["TypeFiler"] = df_leaderboard["TypeName"].apply( lambda m: f"{m} {model_type_emoji[m]}" ) # De-selects but does not filter... # default = df_leaderboard["TypeFiler"].unique().tolist() # default = [(s, s) for s in default if "AutoML" not in s] df_leaderboard["Only Default"] = df_leaderboard["Model"].str.endswith("(default)") df_leaderboard["Only Tuned"] = df_leaderboard["Model"].str.endswith("(tuned)") df_leaderboard["Only Tuned + Ensemble"] = df_leaderboard["Model"].str.endswith( "(tuned + ensemble)" ) | df_leaderboard["Model"].str.endswith("(4h)") # Add Imputed count postfix mask = df_leaderboard["Model"].str.startswith("TabPFNv2") df_leaderboard.loc[mask, "Model"] = ( df_leaderboard.loc[mask, "Model"] + " [35.29% IMPUTED]" ) mask = df_leaderboard["Model"].str.startswith("TabICL") df_leaderboard.loc[mask, "Model"] = ( df_leaderboard.loc[mask, "Model"] + " [29.41% IMPUTED]" ) df_leaderboard["Imputed"] = df_leaderboard["Model"].str.startswith( "TabPFNv2" ) | df_leaderboard["Model"].str.startswith("TabICL") df_leaderboard["Imputed"] = df_leaderboard["Imputed"].replace( { True: "Imputed", False: "Not Imputed", } ) return Leaderboard( value=df_leaderboard, select_columns=SelectColumns( default_selection=list(df_leaderboard.columns), cant_deselect=["Type", "Model"], label="Select Columns to Display:", ), hide_columns=[ "TypeName", "TypeFiler", "RefModel", "Only Default", "Only Tuned", "Only Tuned + Ensemble", "Imputed", ], search_columns=["Model", "Type"], filter_columns=[ ColumnFilter("TypeFiler", type="checkboxgroup", label="Model Types."), ColumnFilter("Only Default", type="boolean", default=False), ColumnFilter("Only Tuned", type="boolean", default=False), ColumnFilter("Only Tuned + Ensemble", type="boolean", default=False), ColumnFilter( "Imputed", type="checkboxgroup", label="(Not) Imputed Models.", info="We impute the performance for models that cannot run on all" " datasets due to task or dataset size constraints (e.g. TabPFN," " TabICL). We impute with the performance of a default RandomForest." " We add a postfix [X% IMPUTED] to the model if any results were" " imputed. The X% shows the percentage of" " datasets that were imputed. In general, imputation negatively" " represents the model performance, punishing the model for not" " being able to run on all datasets.", ), ], bool_checkboxgroup_label="Custom Views (exclusive, only toggle one at a time):", ) def main(): demo = gr.Blocks() with demo: gr.HTML(TITLE) gr.Markdown(INTRODUCTION_TEXT, elem_classes="markdown-text") with gr.Tabs(elem_classes="tab-buttons"): with gr.TabItem("🏅 TabArena-v0.1", elem_id="llm-benchmark-tab-table", id=2): df_leaderboard = load_data("tabarena_leaderboard") make_leaderboard(df_leaderboard) # TODO: decide on which subsets we want to support here. # with gr.TabItem('🏅 Regression', elem_id="llm-benchmark-tab-table", id=0): # df_leaderboard = load_data("leaderboard-regression") # leaderboard = make_leaderboard(df_leaderboard) # # with gr.TabItem('🏅 Classification', elem_id="llm-benchmark-tab-table", id=1): # df_leaderboard = load_data("leaderboard-classification") # leaderboard = make_leaderboard(df_leaderboard) # # with gr.TabItem('🏅 Classification', elem_id="llm-benchmark-tab-table", id=1): # df_leaderboard = load_data("leaderboard-classification") # leaderboard = make_leaderboard(df_leaderboard) # # with gr.TabItem('🏅 TabPFNv2-Compatible', elem_id="llm-benchmark-tab-table", id=1): # df_leaderboard = load_data("leaderboard-classification") # leaderboard = make_leaderboard(df_leaderboard) # # with gr.TabItem('🏅 TabICL-Compatible', elem_id="llm-benchmark-tab-table", id=1): # df_leaderboard = load_data("leaderboard-classification") # leaderboard = make_leaderboard(df_leaderboard) with gr.TabItem("📝 About", elem_id="llm-benchmark-tab-table", id=4): gr.Markdown(ABOUT_TEXT, elem_classes="markdown-text") with gr.Row(), gr.Accordion("📙 Citation", open=False): gr.Textbox( value=CITATION_BUTTON_TEXT, label=CITATION_BUTTON_LABEL, lines=20, elem_id="citation-button", show_copy_button=True, ) scheduler = BackgroundScheduler() # scheduler.add_job(restart_space, "interval", seconds=1800) scheduler.start() demo.queue(default_concurrency_limit=40).launch() demo.launch() if __name__ == "__main__": main()