XGBoost Starter Config Builder

Answer a few questions about your problem and get explained starting parameters and a runnable Python script or Jupyter notebook: correct train / validation / test split, early stopping on validation data only, categorical handling and honest baselines. Everything runs in your browser; no data leaves your machine.

  • Tested with XGBoost 3.4.1 & 2.1.4 (CPU)
  • 270 option combinations executed
  • No sign-up, no upload
  • Last tested 2026-09-21
1. Task
Class balance
3. How are rows related?
5. Hardware
6. Priority
7. Data source in the script

Your starting configuration

Starting parameters

ParameterValueWhy

Watch out for

    Code

    # XGBoost starter script — generated by the xgboost.org Starter Config Builder v1.0.0
    # https://xgboost.org/tools/xgboost-starter-config/
    #
    # Task: Binary classification | Data: 10k – 1M rows | Split: Independent rows (random split)
    # Categorical: No text / categorical columns | Device: CPU | Goal: Quick baseline
    #
    # Tested 2026-09-21 with XGBoost 3.4.1 (Python 3.12) and 2.1.4 (Python 3.11), CPU.
    # This exact option combination was executed end-to-end on synthetic data before release.
    # The parameters are starting points, not tuned or "optimal" values.
    #
    # Install:  pip install "xgboost>=3.4,<3.5" scikit-learn pandas numpy
    #           (XGBoost 3.4 needs Python 3.12+; on older Python, xgboost 2.1.x also runs this script.)
    # Independent resource, not affiliated with the XGBoost project or DMLC.
    
    # %% Imports and version check
    import json
    
    import numpy as np
    import pandas as pd
    import xgboost as xgb
    from sklearn.model_selection import train_test_split
    from sklearn.metrics import accuracy_score, average_precision_score, log_loss, roc_auc_score
    from sklearn.preprocessing import LabelEncoder
    
    if int(xgb.__version__.split(".")[0]) < 2:
        raise RuntimeError(f"This script needs XGBoost >= 2.0 (found {xgb.__version__}): it uses the `device` parameter.")
    print("xgboost", xgb.__version__, "| pandas", pd.__version__, "| numpy", np.__version__)
    
    # %% Settings
    SEED = 42
    # Demo mode: synthetic data is generated below, nothing to download.
    TARGET = "target"
    TEST_SIZE = 0.15   # final, untouched evaluation set
    VALID_SIZE = 0.15  # used for early stopping and any tuning
    
    # %% Demo data
    # Synthetic demo data (20,000 rows) — replace this cell with your own loading code.
    rng = np.random.default_rng(SEED)
    n = 20000
    df = pd.DataFrame({f"num_{i}": rng.normal(size=n) for i in range(6)})
    df.loc[rng.random(n) < 0.05, "num_1"] = np.nan  # missing values are handled natively by XGBoost
    signal = 1.5 * df["num_0"] - df["num_2"] + 0.8 * df["num_1"].fillna(0) * df["num_3"]
    signal = signal + rng.normal(size=n)
    df["target"] = (signal > np.quantile(signal, 0.5)).astype(int)
    print(df.shape)
    print(df.head())
    
    # %% Train / validation / test split
    trainval_df, test_df = train_test_split(df, test_size=TEST_SIZE, random_state=SEED, stratify=df[TARGET])
    train_df, valid_df = train_test_split(
        trainval_df, test_size=VALID_SIZE / (1 - TEST_SIZE), random_state=SEED, stratify=trainval_df[TARGET]
    )
    
    feature_cols = [c for c in df.columns if c not in [TARGET]]
    X_train, X_valid, X_test = (d[feature_cols].copy() for d in (train_df, valid_df, test_df))
    y_train, y_valid, y_test = (d[TARGET] for d in (train_df, valid_df, test_df))
    print(f"rows  train={len(X_train)}  valid={len(X_valid)}  test={len(X_test)}")
    print("train class balance:", y_train.value_counts(normalize=True).round(3).to_dict())
    
    # %% Prepare features (fitted on training data only)
    # Everything below is fitted on the TRAINING split only, then applied to validation/test.
    datetime_cols = [c for c in feature_cols if pd.api.types.is_datetime64_any_dtype(X_train[c])]
    if datetime_cols:
        print("Dropping raw datetime columns (derive explicit features instead):", datetime_cols)
        feature_cols = [c for c in feature_cols if c not in datetime_cols]
        X_train, X_valid, X_test = (X[feature_cols].copy() for X in (X_train, X_valid, X_test))
    cat_cols = [c for c in feature_cols if not pd.api.types.is_numeric_dtype(X_train[c])]
    if cat_cols:
        raise ValueError(
            f"Non-numeric columns found: {cat_cols}. Re-generate the script with a categorical option, "
            "or encode these columns yourself."
        )
    
    label_encoder = LabelEncoder().fit(y_train)
    for name, y_part in (("validation", y_valid), ("test", y_test)):
        unseen = set(y_part) - set(label_encoder.classes_)
        if unseen:
            raise ValueError(f"{name} split has labels never seen in training: {unseen}")
    y_train_enc, y_valid_enc, y_test_enc = (label_encoder.transform(y) for y in (y_train, y_valid, y_test))
    n_classes = len(label_encoder.classes_)
    if n_classes != 2:
        raise ValueError(f"Binary task expects 2 classes, found {n_classes}: {label_encoder.classes_}")
    
    # %% Train with early stopping
    params = dict(
        objective="binary:logistic",  # loss being optimised
        eval_metric="logloss",  # watched on the validation set for early stopping
        tree_method="hist",  # histogram algorithm (fastest; needed for categoricals)
        device="cpu",  # set "cuda" for an NVIDIA GPU
        learning_rate=0.1,  # step size; lower = more trees, often better
        n_estimators=2000,  # upper bound; early stopping picks the real number
        early_stopping_rounds=50,  # patience in boosting rounds
        max_depth=6,  # tree depth; main complexity knob
        min_child_weight=1,  # min hessian per leaf; raise to regularise
        subsample=0.8,  # row sampling per tree
        colsample_bytree=0.8,  # column sampling per tree
        reg_lambda=1.0,  # L2 penalty on leaf weights
        random_state=SEED,
        n_jobs=-1,  # all CPU cores
    )
    model = xgb.XGBClassifier(**params)
    model.fit(X_train, y_train_enc, eval_set=[(X_valid, y_valid_enc)], verbose=200)
    print(f"best iteration: {model.best_iteration}  best validation {params['eval_metric']}: {model.best_score:.5f}")
    
    # %% Evaluate once on the test set
    # The test split is used exactly once, after all choices were made on validation data.
    test_proba = model.predict_proba(X_test)[:, 1]
    positive_rate = float(np.mean(y_test_enc))
    prior = float(np.mean(y_train_enc))
    results = {
        "roc_auc": roc_auc_score(y_test_enc, test_proba),
        "pr_auc": average_precision_score(y_test_enc, test_proba),
        "pr_auc_no_skill": positive_rate,
        "log_loss": log_loss(y_test_enc, test_proba, labels=[0, 1]),
        "log_loss_constant_baseline": log_loss(y_test_enc, np.full(len(y_test_enc), prior), labels=[0, 1]),
        "accuracy_at_0.5": accuracy_score(y_test_enc, (test_proba >= 0.5).astype(int)),
    }
    for k, v in results.items():
        print(f"{k:>28}: {v:.4f}")
    
    # %% Feature importance and saving
    gain = model.get_booster().get_score(importance_type="gain")
    print("top features by gain:")
    for name, value in sorted(gain.items(), key=lambda kv: -kv[1])[:10]:
        print(f"  {name:<20} {value:.2f}")
    
    # JSON keeps categorical split information; the legacy binary format does not.
    model.save_model("xgb_model.json")
    metadata = {"feature_cols": feature_cols, "xgboost_version": xgb.__version__, "best_iteration": int(model.best_iteration)}
    metadata["classes"] = [str(c) for c in label_encoder.classes_]
    with open("xgb_model_metadata.json", "w") as f:
        json.dump(metadata, f, indent=2)
    print("saved xgb_model.json and xgb_model_metadata.json")
    

    The .py file uses # %% cell markers (VS Code, Jupytext, Spyder); the notebook has the same cells with short explanations.

    What this tool is, and is not

    • Starting points, not tuned values. The parameters follow common practice and the official parameter reference. They are meant to give you a sound baseline to improve on your own validation data, not the best values for your problem.
    • Scope: binary and multiclass classification and regression on tabular data with the scikit-learn API. Ranking, survival, multi-output, custom objectives, distributed training (Dask, Spark, Ray) and external-memory training are not covered.
    • GPU: the device="cuda" option follows the documentation but has not been executed by us (no GPU in our test runner). Everything else has been.
    • Your data never leaves the page. Code is generated in your browser. File paths and column names you type go into the script only; they are not stored, not put in the URL and not sent to our analytics.
    • Leakage the code cannot see: columns derived from the target or recorded after the outcome must be removed by you.

    How it was tested

    On 2026-09-21 every one of the 270 CPU option combinations was generated and executed end-to-end on synthetic data with XGBoost 3.4.1 (Python 3.12, scikit-learn 1.9.1, pandas 3.0.6), and again on XGBoost 2.1.4 (Python 3.11, pandas 2.3.3). Each run had to pass these checks:

    • early stopping triggered, and predict() uses the best iteration
    • the saved JSON model reloads and reproduces the test predictions
    • the model beats a trivial baseline on the untouched test split
    • no test data in training or early stopping; encoders and category levels fitted on training data only
    • every non-target column kept as a feature, and no deprecation warnings raised by the generated code

    CSV mode was run with awkward column names (spaces, quotes) and a deliberately wrong setup to confirm it fails with a clear message. A sample of notebooks was executed cell by cell (XGBoost 3.4.1). Parameter names and defaults were checked against the official XGBoost parameter reference.

    Builder v1.0.0. Independent tool, not affiliated with the XGBoost project or DMLC. Found a problem? Tell us.

    Planned · not available yet

    Production Template Pack for XGBoost

    The free builder gives you a correct baseline. We are considering a paid pack for the steps that come after it, as the same kind of tested, version-pinned code:

    • Hyperparameter search (Optuna) that never touches the test set
    • SHAP explanation report, plus probability calibration and decision-threshold selection
    • Batch and HTTP inference templates, input checks and drift monitoring
    • pytest suite and a model card template

    We will only build it if enough people ask for it. Leave your email and we will write once, when it is available or if we decide not to build it. This is not a newsletter and there is no payment here.

    Parts you would actually use (optional)
    What would you consider paying for it? (optional, one-time)

    What we store, and how to delete it

    Only the answers in this form, the page you sent it from and your country as reported by Cloudflare. No IP address, no tracking cookie, nothing from the config builder. Stored in a Cloudflare D1 database operated by this site, not shared or sold, and used only for the single email described above.

    To delete it, enter the same email and press the button below.