Ecosystem Integration

GnuplotEx integrates seamlessly with the Elixir machine learning and data science ecosystem, providing direct plotting support for Nx tensors, Explorer DataFrames, and specialized ML visualization helpers.

Installation

All ecosystem integrations are optional. Add the dependencies you need to your mix.exs:

def deps do
  [
    {:gnuplot_ex, "~> 0.1"},
    # Optional: For Nx tensor support
    {:nx, "~> 0.7"},
    # Optional: For DataFrame support (uses Polars backend)
    {:explorer, "~> 0.8"}
  ]
end

Nx Tensor Support

GnuplotEx can directly plot Nx tensors with automatic dimension handling.

1D Tensors (Line Plots)

1D tensors are treated as y-values with auto-generated x indices:

tensor = Nx.tensor([1.0, 4.0, 2.0, 8.0, 5.0, 7.0, 3.0])

GnuplotEx.new()
|> GnuplotEx.line(tensor, label: "Signal")
|> GnuplotEx.title("1D Tensor as Line Plot")
|> GnuplotEx.render(:svg)
1D Tensor Line Plot

2D Tensors (Scatter/Line Plots)

2D tensors with 2 columns are treated as [x, y] points:

# Generate some 2D points
tensor = Nx.tensor([
  [1.0, 2.0],
  [2.0, 4.0],
  [3.0, 6.0],
  [4.0, 8.0],
  [5.0, 10.0]
])

GnuplotEx.new()
|> GnuplotEx.scatter(tensor, label: "Linear Relationship")
|> GnuplotEx.title("2D Tensor Scatter Plot")
|> GnuplotEx.render(:svg)
2D Tensor Scatter Plot

2D Tensors as Heatmaps

2D tensors with more than 3 columns are treated as grid data for heatmaps:

# Create a 2D matrix for heatmap
matrix = Nx.tensor([
  [1, 2, 3, 4, 5],
  [2, 4, 6, 8, 10],
  [3, 6, 9, 12, 15],
  [4, 8, 12, 16, 20],
  [5, 10, 15, 20, 25]
])

# Convert to list for surface plots
GnuplotEx.new()
|> GnuplotEx.surface(Nx.to_list(matrix))
|> GnuplotEx.palette(:viridis)
|> GnuplotEx.title("2D Matrix Heatmap")
|> GnuplotEx.render(:svg)
2D Matrix Heatmap

3D Tensors (Scatter Plots)

2D tensors with 3 columns are treated as [x, y, z] points:

# 3D point cloud
tensor = Nx.tensor([
  [1.0, 2.0, 3.0],
  [2.0, 3.0, 4.0],
  [3.0, 4.0, 5.0],
  [4.0, 5.0, 6.0]
])

GnuplotEx.new()
|> GnuplotEx.scatter3d(tensor, label: "3D Points")
|> GnuplotEx.title("3D Tensor Scatter")
|> GnuplotEx.render(:svg)
3D Tensor Scatter

Explorer DataFrame Support

Explorer DataFrames can be plotted directly with automatic column type detection.

Basic Usage

alias Explorer.DataFrame

df = DataFrame.new(%{
  x: [1, 2, 3, 4, 5],
  y: [2, 4, 6, 8, 10]
})

GnuplotEx.new()
|> GnuplotEx.scatter(df, label: "DataFrame Points")
|> GnuplotEx.title("DataFrame Scatter Plot")
|> GnuplotEx.render(:svg)
DataFrame Scatter Plot

Column Selection

Explicitly specify which columns to use:

df = DataFrame.new(%{
  time: [1, 2, 3, 4, 5],
  temperature: [20, 22, 21, 23, 22],
  humidity: [50, 52, 48, 55, 53]
})

# Plot temperature over time
GnuplotEx.new()
|> GnuplotEx.line(df, x: :time, y: :temperature, label: "Temperature")
|> GnuplotEx.title("Temperature Over Time")
|> GnuplotEx.render(:svg)

# Or use explicit columns list
GnuplotEx.new()
|> GnuplotEx.line(df, columns: [:time, :humidity], label: "Humidity")
|> GnuplotEx.render(:svg)
Temperature Time Series

Working with Real Data

# Load CSV data
df = Explorer.DataFrame.from_csv!("data.csv")

# Auto-detect columns (uses first two numeric columns)
GnuplotEx.new()
|> GnuplotEx.scatter(df)
|> GnuplotEx.title("Data Analysis")
|> GnuplotEx.render(:svg)

ML Visualization Helpers

GnuplotEx includes specialized helpers for common machine learning visualization tasks.

Loss Curves

Plot training and validation loss over epochs:

alias GnuplotEx.ML.Loss

train_loss = [0.9, 0.7, 0.5, 0.35, 0.25, 0.18, 0.12, 0.08]
val_loss = [0.95, 0.75, 0.55, 0.42, 0.35, 0.30, 0.28, 0.27]

Loss.plot(train_loss, val_loss,
  title: "Model Training Progress",
  x_label: "Epoch",
  y_label: "Cross-Entropy Loss"
)
|> GnuplotEx.render(:svg)
Training Loss Curves

Plot multiple metrics:

metrics = %{
  train_loss: [0.9, 0.7, 0.5, 0.3, 0.2],
  val_loss: [0.95, 0.75, 0.55, 0.4, 0.35],
  train_acc: [0.6, 0.7, 0.8, 0.88, 0.92],
  val_acc: [0.55, 0.68, 0.78, 0.82, 0.85]
}

Loss.plot_metrics(metrics,
  title: "Training Metrics",
  x_label: "Epoch"
)
|> GnuplotEx.render(:svg)
Training Metrics

Confusion Matrix

Visualize classification results:

alias GnuplotEx.ML.Confusion

# Binary classification
matrix = [
  [85, 15],  # True Negative, False Positive
  [10, 90]   # False Negative, True Positive
]
classes = ["Negative", "Positive"]

Confusion.plot(matrix, classes,
  title: "Binary Classification Results"
)
|> GnuplotEx.render(:svg)

Multi-class with normalization:

matrix = [
  [50, 3, 2],
  [4, 45, 1],
  [2, 2, 46]
]
classes = ["Cat", "Dog", "Bird"]

Confusion.plot(matrix, classes,
  normalize: true,
  title: "Normalized Confusion Matrix"
)
|> GnuplotEx.render(:svg)
Confusion Matrix

Calculate metrics from confusion matrix:

accuracy = Confusion.accuracy(matrix)    # Overall accuracy
precision = Confusion.precision(matrix)  # Per-class precision
recall = Confusion.recall(matrix)        # Per-class recall
f1 = Confusion.f1_score(matrix)          # Per-class F1 score

ROC Curves

Plot Receiver Operating Characteristic curves:

alias GnuplotEx.ML.ROC

# From precomputed FPR/TPR
fpr = [0.0, 0.1, 0.2, 0.4, 0.6, 1.0]
tpr = [0.0, 0.5, 0.7, 0.85, 0.95, 1.0]

ROC.plot(fpr, tpr,
  auc: 0.87,
  title: "ROC Curve"
)
|> GnuplotEx.render(:svg)

Calculate ROC curve from scores and labels:

scores = [0.9, 0.8, 0.7, 0.6, 0.4, 0.3, 0.2, 0.1]
labels = [1, 1, 0, 1, 0, 1, 0, 0]

{fpr, tpr, _thresholds} = ROC.calculate_curve(scores, labels)
auc = ROC.calculate_auc(fpr, tpr)

ROC.plot(fpr, tpr, auc: auc)
|> GnuplotEx.render(:svg)
ROC Curve

Multi-class ROC curves:

roc_data = %{
  "Cat" => {fpr_cat, tpr_cat, 0.92},
  "Dog" => {fpr_dog, tpr_dog, 0.88},
  "Bird" => {fpr_bird, tpr_bird, 0.95}
}

ROC.plot_multiclass(roc_data,
  title: "One-vs-Rest ROC Curves"
)
|> GnuplotEx.render(:svg)

Embedding Visualization

Visualize dimensionality reduction results (t-SNE, UMAP, PCA):

alias GnuplotEx.ML.Embeddings

# 2D embeddings with labels
embeddings = [
  [1.2, 3.4], [1.5, 3.1], [1.3, 3.8],  # Class 0
  [5.2, 6.1], [5.5, 6.3], [5.1, 5.9],  # Class 1
  [3.0, 1.2], [3.2, 1.5], [2.8, 1.1]   # Class 2
]
labels = [0, 0, 0, 1, 1, 1, 2, 2, 2]

Embeddings.plot(embeddings, labels,
  label_names: ["Cluster A", "Cluster B", "Cluster C"],
  title: "t-SNE Visualization"
)
|> GnuplotEx.render(:svg)
2D Embeddings

3D embeddings:

embeddings_3d = [
  [1, 2, 3], [1.5, 2.5, 3.5],
  [5, 6, 7], [5.5, 6.5, 7.5]
]
labels = [0, 0, 1, 1]

Embeddings.plot(embeddings_3d, labels,
  title: "3D t-SNE"
)
|> GnuplotEx.render(:svg)
3D Embeddings

Complete ML Training Example

Here's a complete example showing how to visualize a training loop:

defmodule TrainingVisualizer do
  alias GnuplotEx.ML.{Loss, Confusion, ROC}

  def visualize_training(history, predictions, labels) do
    # 1. Plot training curves
    loss_plot = Loss.plot(
      history.train_loss,
      history.val_loss,
      title: "Training Progress"
    )
    GnuplotEx.save(loss_plot, "training_loss.svg")

    # 2. Plot all metrics
    metrics_plot = Loss.plot_metrics(%{
      train_loss: history.train_loss,
      val_loss: history.val_loss,
      train_acc: history.train_acc,
      val_acc: history.val_acc
    })
    GnuplotEx.save(metrics_plot, "training_metrics.svg")

    # 3. Confusion matrix
    cm = build_confusion_matrix(predictions, labels)
    cm_plot = Confusion.plot(cm, ["Class A", "Class B", "Class C"],
      normalize: true
    )
    GnuplotEx.save(cm_plot, "confusion_matrix.svg")

    # 4. ROC curve
    {fpr, tpr, _} = ROC.calculate_curve(predictions, labels)
    auc = ROC.calculate_auc(fpr, tpr)
    roc_plot = ROC.plot(fpr, tpr, auc: auc)
    GnuplotEx.save(roc_plot, "roc_curve.svg")

    :ok
  end

  defp build_confusion_matrix(predictions, labels) do
    # Build confusion matrix from predictions and labels
    # ... implementation
  end
end

Integration with Axon

If you're using Axon for neural networks, you can integrate GnuplotEx for visualization:

defmodule AxonTrainer do
  require Axon

  def train_with_visualization(model, train_data, val_data) do
    history = %{train_loss: [], val_loss: [], train_acc: [], val_acc: []}

    model
    |> Axon.Loop.trainer(:mean_squared_error, Axon.Optimizers.adam(0.001))
    |> Axon.Loop.metric(:accuracy)
    |> Axon.Loop.handle_event(:epoch_completed, fn state ->
      # Collect metrics each epoch
      history = update_history(history, state)

      # Live plot every 10 epochs
      if rem(state.epoch, 10) == 0 do
        plot_live(history)
      end

      {:continue, state}
    end)
    |> Axon.Loop.run(train_data, epochs: 100)

    history
  end

  defp plot_live(history) do
    GnuplotEx.ML.Loss.plot(history.train_loss, history.val_loss)
    |> GnuplotEx.render(:svg)
    |> then(fn {:ok, svg} -> File.write!("live_training.svg", svg) end)
  end
end

Performance Tips

  1. Use streams for large datasets: GnuplotEx uses streaming to handle large Nx tensors and DataFrames efficiently.

  2. Cache rendered plots: When using LiveView, enable caching to avoid re-rendering unchanged plots:

    <.live_gnuplot plot={@plot} cache={true} cache_ttl={60_000} />
  3. Batch updates: When plotting real-time data, batch multiple points before updating the plot.

  4. Choose appropriate formats: Use :svg for vector graphics (web), :png for raster images.

Troubleshooting

"No numeric columns found" Error

Explorer DataFrames need at least one numeric column for plotting. Check your column types:

Explorer.DataFrame.dtypes(df)

Nx Tensor Shape Errors

Ensure your tensor has the expected shape:

Nx.shape(tensor)  # Should be {n}, {n, 2}, {n, 3}, or {rows, cols}

Memory Issues with Large Datasets

For very large datasets, consider downsampling before plotting:

# Downsample Nx tensor
tensor
|> Nx.to_list()
|> Enum.take_every(10)
|> Nx.tensor()

# Downsample DataFrame
df
|> Explorer.DataFrame.sample(1000)