Skip to main content
This page shows you how to create and log W&B Tables with the Python SDK so you can visualize and analyze tabular data, including predictions, evaluation results, and batched training output, alongside your ML experiments. A W&B Table is a two-dimensional grid of data where each column has a single type of data. Each row represents one or more data points logged to a W&B run. W&B Tables support primitive and numeric types, as well as nested lists, dictionaries, and rich media types. A W&B Table is a specialized data type in W&B, logged as an artifact object. You create and log table objects using the W&B Python SDK. When you create a table object, you specify the columns and data for the table and a mode. The mode determines how the table is logged and updated during your ML experiments, which affects performance, what you can change after logging, and how the table appears in the W&B App.
INCREMENTAL mode is supported on W&B Server v0.70.0 and above.

Create and log a table

Follow these steps to log a table to a run. The resulting table is stored as an artifact in W&B and rendered in the run’s workspace.
  1. Initialize a new run with wandb.init().
  2. Create a Table object with the wandb.Table class. Specify the columns and data for the table for the columns and data parameters, respectively. Set the optional log_mode parameter to one of the three modes: IMMUTABLE (the default), MUTABLE, or INCREMENTAL, because the mode controls how the table behaves when logged. See Logging modes for more information.
  3. Log the table to W&B with run.log().
The following example shows how to create and log a table with two columns, a and b, and two rows of data, ["a1", "b1"] and ["a2", "b2"]:
import wandb

# Start a new run
with wandb.init(project="table-demo") as run:

    # Create a table object with two columns and two rows of data
    my_table = wandb.Table(
        columns=["a", "b"],
        data=[["a1", "b1"], ["a2", "b2"]],
        log_mode="IMMUTABLE"
        )

    # Log the table to W&B
    run.log({"Table Name": my_table})

Logging modes

The logging mode you choose affects performance, what you can change after logging, and how the table appears in the W&B App. Pick the mode that matches your workflow, for example, a single end-of-run snapshot, a table you progressively enrich with new columns, or a long-running training table updated in batches. The wandb.Table log_mode parameter determines how a table is logged and updated during your ML experiments. The log_mode parameter accepts one of three arguments: IMMUTABLE, MUTABLE, and INCREMENTAL. Each mode has different implications for how a table is logged, how it can be modified, and how it is rendered in the W&B App. The following describes the three logging modes, the high-level differences, and common use cases for each mode:
ModeDefinitionUse casesBenefits
IMMUTABLEOnce a table is logged to W&B, you cannot modify it.- Storing tabular data generated at the end of a run for further analysis- Minimal overhead when logged at the end of a run
- All rows rendered in UI
MUTABLEAfter you log a table to W&B, you can overwrite the existing table with a new one.- Adding columns or rows to existing tables
- Enriching results with new information
- Capture table mutations
- All rows rendered in UI
INCREMENTALAdd batches of new rows to a table throughout the machine learning experiment.- Adding rows to tables in batches
- Long-running training jobs
- Processing large datasets in batches
- Monitoring ongoing results
- View updates on UI during training
- Ability to step through increments
The following sections show example code snippets for each mode along with considerations for when to use each mode.

MUTABLE mode

MUTABLE mode updates an existing table by replacing it with a new one. MUTABLE mode is useful when you want to add new columns and rows to an existing table in a non-iterative process. Within the UI, the table is rendered with all rows and columns, including the new ones added after the initial log.
In MUTABLE mode, the table object is replaced each time you log the table. Overwriting a table with a new one is computationally expensive and can be slow for large tables.
The following example shows how to create a table in MUTABLE mode, log it, and then add new columns to it. The table object is logged three times: once with the initial data, once with the confidence scores, and once with the final predictions.
The following example uses a placeholder function load_eval_data() to load data and a placeholder function model.predict() to make predictions. Replace these with your own data loading and prediction functions.
import wandb
import numpy as np

with wandb.init(project="mutable-table-demo") as run:

    # Create a table object with MUTABLE logging mode
    table = wandb.Table(columns=["input", "label", "prediction"],
                        log_mode="MUTABLE")

    # Load data and make predictions
    inputs, labels = load_eval_data() # Placeholder function
    raw_preds = model.predict(inputs) # Placeholder function

    for inp, label, pred in zip(inputs, labels, raw_preds):
        table.add_data(inp, label, pred)

    # Step 1: Log initial data
    run.log({"eval_table": table})  # Log initial table

    # Step 2: Add confidence scores (e.g. max softmax)
    confidences = np.max(raw_preds, axis=1)
    table.add_column("confidence", confidences)
    run.log({"eval_table": table})  # Add confidence info

    # Step 3: Add post-processed predictions
    # (e.g., thresholded or smoothed outputs)
    post_preds = (confidences > 0.7).astype(int)
    table.add_column("final_prediction", post_preds)
    run.log({"eval_table": table})  # Final update with another column
If you only want to add new batches of rows (no columns) incrementally like in a training loop, consider using INCREMENTAL mode instead.

INCREMENTAL mode

In INCREMENTAL mode, you log batches of rows to a table during the machine learning experiment. This is ideal for monitoring long-running jobs or when working with large tables that would be inefficient to log during the run for updates. Within the UI, the table is updated with new rows as they are logged, so you can view the latest data without waiting for the entire run to finish. You can also step through the increments to view the table at different points in time.
Run workspaces in the W&B App have a limit of 100 increments. If you log more than 100 increments, only the most recent 100 are shown in the run workspace.
The following example creates a table in INCREMENTAL mode, logs it, and then adds new rows to it. The table is logged once per training step (step).
The following example uses a placeholder function get_training_batch() to load data, a placeholder function train_model_on_batch() to train the model, and a placeholder function predict_on_batch() to make predictions. Replace these with your own data loading, training, and prediction functions.
import wandb

with wandb.init(project="incremental-table-demo") as run:

    # Create a table with INCREMENTAL logging mode
    table = wandb.Table(columns=["step", "input", "label", "prediction"],
                        log_mode="INCREMENTAL")

    # Training loop
    for step in range(get_num_batches()): # Placeholder function
        # Load batch data
        inputs, labels = get_training_batch(step) # Placeholder function

        # Train and predict
        train_model_on_batch(inputs, labels) # Placeholder function
        predictions = predict_on_batch(inputs) # Placeholder function

        # Add batch data to table
        for input_item, label, prediction in zip(inputs, labels, predictions):
            table.add_data(step, input_item, label, prediction)

        # Log the table incrementally
        run.log({"training_table": table}, step=step)
Incremental logging is more computationally efficient than logging a new table each time (log_mode="MUTABLE"). However, the W&B App may not render all rows in the table if you log a large number of increments. To update and view your table data while your run is ongoing and to have all the data available for analysis, consider using two tables: one with INCREMENTAL log mode and one with IMMUTABLE log mode. The following example shows how to combine INCREMENTAL and IMMUTABLE logging modes to achieve this.
import wandb

with wandb.init(project="combined-logging-example") as run:

    # Create an incremental table for efficient updates during training
    incr_table = wandb.Table(columns=["step", "input", "prediction", "label"],
                            log_mode="INCREMENTAL")

    # Training loop
    for step in range(get_num_batches()):
        # Process batch
        inputs, labels = get_training_batch(step)
        predictions = model.predict(inputs)

        # Add data to incremental table
        for inp, pred, label in zip(inputs, predictions, labels):
            incr_table.add_data(step, inp, pred, label)

        # Log the incremental update (suffix with -incr to distinguish from final table)
        run.log({"table-incr": incr_table}, step=step)

    # At the end of training, create a complete immutable table with all data
    # Using the default IMMUTABLE mode to preserve the complete dataset
    final_table = wandb.Table(columns=incr_table.columns, data=incr_table.data, log_mode="IMMUTABLE")
    run.log({"table": final_table})
In this example, the incr_table is logged incrementally (with log_mode="INCREMENTAL") during training. This lets you log and view updates to the table as new data is processed. At the end of training, an immutable table (final_table) is created with all data from the incremental table. The immutable table is logged to preserve the complete dataset for further analysis and to let you view all rows in the W&B App.

Examples

The following examples show end-to-end logging patterns for common workflows: enriching evaluation results, resuming runs that log incremental tables, and incrementally logging batches during training.

Enrich evaluation results with MUTABLE

import wandb
import numpy as np

with wandb.init(project="mutable-logging") as run:

    # Step 1: Log initial predictions
    table = wandb.Table(columns=["input", "label", "prediction"], log_mode="MUTABLE")
    inputs, labels = load_eval_data()
    raw_preds = model.predict(inputs)

    for inp, label, pred in zip(inputs, labels, raw_preds):
        table.add_data(inp, label, pred)

    run.log({"eval_table": table})  # Log raw predictions

    # Step 2: Add confidence scores (e.g. max softmax)
    confidences = np.max(raw_preds, axis=1)
    table.add_column("confidence", confidences)
    run.log({"eval_table": table})  # Add confidence info

    # Step 3: Add post-processed predictions
    # (e.g., thresholded or smoothed outputs)
    post_preds = (confidences > 0.7).astype(int)
    table.add_column("final_prediction", post_preds)
    run.log({"eval_table": table})

Resume runs with INCREMENTAL tables

You can continue logging to an incremental table when resuming a run:
# Start or resume a run
resumed_run = wandb.init(project="resume-incremental", id="your-run-id", resume="must")

# Create the incremental table; no need to populate with data from previously logged table
# Increments continue to be added to the Table artifact.
table = wandb.Table(columns=["step", "metric"], log_mode="INCREMENTAL")

# Continue logging
for step in range(resume_step, final_step):
    metric = compute_metric(step)
    table.add_data(step, metric)
    resumed_run.log({"metrics": table}, step=step)

resumed_run.finish()
Increments are logged to a new table if you turn off summaries on a key used for the incremental table using wandb.Run.define_metric("<table_key>", summary="none") or wandb.Run.define_metric("*", summary="none").

Train in batches with INCREMENTAL


with wandb.init(project="batch-training-incremental") as run:

    # Create an incremental table
    table = wandb.Table(columns=["step", "input", "label", "prediction"], log_mode="INCREMENTAL")

    # Simulated training loop
    for step in range(get_num_batches()):
        # Load batch data
        inputs, labels = get_training_batch(step)

        # Train the model on this batch
        train_model_on_batch(inputs, labels)

        # Run model inference
        predictions = predict_on_batch(inputs)

        # Add data to the table
        for input_item, label, prediction in zip(inputs, labels, predictions):
            table.add_data(step, input_item, label, prediction)

        # Log the current state of the table incrementally
        run.log({"training_table": table}, step=step)