> For the complete documentation index, see [llms.txt](https://docs.seldon.ai/mpm/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.seldon.ai/mpm/visualizing-metrics/visualization-python.md).

# Visualization in Python

To visualize the metrics API response, you can use the following Python code in a Jupyter notebook.

## Classification Metrics

This example demonstrates how to plot both a confusion matrix and the performance metrics (e.g., accuracy, F1, precision, recall, specificity) over time.

First, we need to fetch the metrics for a specific time window.

{% code overflow="wrap" lineNumbers="true" %}

```python
import requests

url = f"http://{CLUSTER_IP}/metrics-server/api/v1/metrics/pipeline/classification"

params = {
    'namespace': 'seldon',
    'pipelineName': 'iris-model-pipeline',
    'modelName': 'iris-model',
    'startTime': '2025-02-25T11:51:22Z',
    'endTime': '2025-02-25T11:53:22Z',
    'interval': '10s'
}

response = requests.get(url, params=params)
```

{% endcode %}

<details>

<summary>Expand to see an example of metrics API response</summary>

{% code overflow="wrap" lineNumbers="true" %}

```json
{
  "metrics": [
    {
      "accuracy": 0,
      "confusionMatrix": {
        "categories": [
          "Setosa",
          "Versicolor",
          "Virginica"
        ],
        "computedConfusionValues": [
          {
            "falseNegativeCount": 10,
            "falsePositiveCount": 0,
            "trueNegativeCount": 0,
            "truePositiveCount": 0
          },
          {
            "falseNegativeCount": 0,
            "falsePositiveCount": 10,
            "trueNegativeCount": 0,
            "truePositiveCount": 0
          },
          {
            "falseNegativeCount": 0,
            "falsePositiveCount": 0,
            "trueNegativeCount": 10,
            "truePositiveCount": 0
          }
        ],
        "values": [
          0,
          10,
          0,
          0,
          0,
          0,
          0,
          0,
          0
        ]
      },
      "endTime": "2025-02-25T11:51:32Z",
      "f1": 0,
      "precision": 0,
      "recall": 0,
      "specificity": 0.5
    },
    {
      "accuracy": 0,
      "confusionMatrix": {
        "categories": [
          "Setosa",
          "Versicolor",
          "Virginica"
        ],
        "computedConfusionValues": [
          {
            "falseNegativeCount": 16,
            "falsePositiveCount": 0,
            "trueNegativeCount": 0,
            "truePositiveCount": 0
          },
          {
            "falseNegativeCount": 0,
            "falsePositiveCount": 16,
            "trueNegativeCount": 0,
            "truePositiveCount": 0
          },
          {
            "falseNegativeCount": 0,
            "falsePositiveCount": 0,
            "trueNegativeCount": 16,
            "truePositiveCount": 0
          }
        ],
        "values": [
          0,
          16,
          0,
          0,
          0,
          0,
          0,
          0,
          0
        ]
      },
      "endTime": "2025-02-25T11:51:42Z",
      "f1": 0,
      "precision": 0,
      "recall": 0,
      "specificity": 0.5
    },
    {
      "accuracy": 0.4375,
      "confusionMatrix": {
        "categories": [
          "Setosa",
          "Versicolor",
          "Virginica"
        ],
        "computedConfusionValues": [
          {
            "falseNegativeCount": 9,
            "falsePositiveCount": 0,
            "trueNegativeCount": 7,
            "truePositiveCount": 0
          },
          {
            "falseNegativeCount": 0,
            "falsePositiveCount": 9,
            "trueNegativeCount": 0,
            "truePositiveCount": 7
          },
          {
            "falseNegativeCount": 0,
            "falsePositiveCount": 0,
            "trueNegativeCount": 16,
            "truePositiveCount": 0
          }
        ],
        "values": [
          0,
          9,
          0,
          0,
          7,
          0,
          0,
          0,
          0
        ]
      },
      "endTime": "2025-02-25T11:51:52Z",
      "f1": 0.46666667,
      "precision": 0.4375,
      "recall": 0.5,
      "specificity": 0.6666667
    },
    {
      "accuracy": 0.125,
      "confusionMatrix": {
        "categories": [
          "Setosa",
          "Versicolor",
          "Virginica"
        ],
        "computedConfusionValues": [
          {
            "falseNegativeCount": 2,
            "falsePositiveCount": 0,
            "trueNegativeCount": 6,
            "truePositiveCount": 0
          },
          {
            "falseNegativeCount": 0,
            "falsePositiveCount": 7,
            "trueNegativeCount": 0,
            "truePositiveCount": 1
          },
          {
            "falseNegativeCount": 5,
            "falsePositiveCount": 0,
            "trueNegativeCount": 3,
            "truePositiveCount": 0
          }
        ],
        "values": [
          0,
          2,
          0,
          0,
          1,
          0,
          0,
          5,
          0
        ]
      },
      "endTime": "2025-02-25T11:52:02Z",
      "f1": 0.18181819,
      "precision": 0.125,
      "recall": 0.33333334,
      "specificity": 0.6666667
    },
    {
      "accuracy": -1,
      "confusionMatrix": {
        "categories": [],
        "computedConfusionValues": [],
        "values": []
      },
      "endTime": "2025-02-25T11:52:12Z",
      "f1": -1,
      "precision": -1,
      "recall": -1,
      "specificity": -1
    },
    {
      "accuracy": -1,
      "confusionMatrix": {
        "categories": [],
        "computedConfusionValues": [],
        "values": []
      },
      "endTime": "2025-02-25T11:52:22Z",
      "f1": -1,
      "precision": -1,
      "recall": -1,
      "specificity": -1
    },
    {
      "accuracy": -1,
      "confusionMatrix": {
        "categories": [],
        "computedConfusionValues": [],
        "values": []
      },
      "endTime": "2025-02-25T11:52:32Z",
      "f1": -1,
      "precision": -1,
      "recall": -1,
      "specificity": -1
    },
    {
      "accuracy": -1,
      "confusionMatrix": {
        "categories": [],
        "computedConfusionValues": [],
        "values": []
      },
      "endTime": "2025-02-25T11:52:42Z",
      "f1": -1,
      "precision": -1,
      "recall": -1,
      "specificity": -1
    },
    {
      "accuracy": -1,
      "confusionMatrix": {
        "categories": [],
        "computedConfusionValues": [],
        "values": []
      },
      "endTime": "2025-02-25T11:52:52Z",
      "f1": -1,
      "precision": -1,
      "recall": -1,
      "specificity": -1
    },
    {
      "accuracy": -1,
      "confusionMatrix": {
        "categories": [],
        "computedConfusionValues": [],
        "values": []
      },
      "endTime": "2025-02-25T11:53:02Z",
      "f1": -1,
      "precision": -1,
      "recall": -1,
      "specificity": -1
    },
    {
      "accuracy": -1,
      "confusionMatrix": {
        "categories": [],
        "computedConfusionValues": [],
        "values": []
      },
      "endTime": "2025-02-25T11:53:12Z",
      "f1": -1,
      "precision": -1,
      "recall": -1,
      "specificity": -1
    },
    {
      "accuracy": -1,
      "confusionMatrix": {
        "categories": [],
        "computedConfusionValues": [],
        "values": []
      },
      "endTime": "2025-02-25T11:53:22Z",
      "f1": -1,
      "precision": -1,
      "recall": -1,
      "specificity": -1
    }
  ]
}
```

{% endcode %}

</details>

Next, we can build a confusion matrix for the selected time bucket. For more details on time bucketing, refer to the ["Calculating Metrics"](/mpm/supported-metrics/calculating-metrics.md#time-bucketing) page.

{% code overflow="wrap" lineNumbers="true" %}

```python
%matplotlib inline

import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
import pandas as pd

json_data = response.json()

# Choosing a time bucket
time_bucket_index = 0
confusion_matrix_data = json_data['metrics'][time_bucket_index]['confusionMatrix']

# Extracting the confusion matrix values
categories = confusion_matrix_data['categories']
values = confusion_matrix_data['values']

# Reshape the values to form the confusion matrix
matrix_size = len(categories)
confusion_matrix = np.array(values).reshape((matrix_size, matrix_size))

# Creating a pandas dataframe for better visualization
df_cm = pd.DataFrame(confusion_matrix, index=categories, columns=categories)

# Plotting the confusion matrix
plt.figure(figsize=(8, 6))
sns.heatmap(df_cm, annot=True, fmt='g', cmap='Blues')
plt.title('Confusion Matrix')
plt.xlabel('Predicted Labels')
plt.ylabel('Actual Labels')
plt.show()
```

{% endcode %}

![Example of a Confusion matrix using test data](https://4156402519-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4sbtWGU6pWCb5XK4PJN%2Fuploads%2Fgit-blob-cf3effa316744d1ba2cfe71caade2316dcdf1019%2Fconfusion-matrix.png?alt=media)

Finally, we can plot the Accuracy, Precision, Recall, F1, and Specificity metrics over time.

{% code overflow="wrap" lineNumbers="true" %}

```python
%matplotlib inline

import matplotlib.pyplot as plt
import pandas as pd

df = pd.DataFrame(json_data['metrics'])

# Plotting the data
plt.figure(figsize=(10, 6))

for metric in ['accuracy', 'f1', 'precision', 'recall', 'specificity']:
    df_filtered = df[df[metric] != -1]
    plt.plot(df_filtered['endTime'], df_filtered[metric], label=metric)

plt.xlabel('Time')
plt.ylabel('Metrics')
plt.title('Model Performance Metrics Over Time')
plt.legend()
plt.grid(True)
plt.xticks(rotation=45)
plt.tight_layout()

plt.show()
```

{% endcode %}

![Example of a Classification Metrics plot using test data](https://4156402519-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4sbtWGU6pWCb5XK4PJN%2Fuploads%2Fgit-blob-f77d90263e9ed375aa84d4cb01a3f9eea1b8ce70%2Fclassification-metrics-plot.png?alt=media)

## Regression Metrics

This example demonstrates how to plot such performance metrics as Mean Absolute Error, Mean Squared Error, Root Mean Squared Error over time.

Let's fetch the metrics over specific time window first.

{% code overflow="wrap" lineNumbers="true" %}

```python
import requests

url = f"http://{CLUSTER_IP}/metrics-server/api/v1/metrics/pipeline/regression"

params = {
    'namespace': 'seldon',
    'pipelineName': 'house-value-pipeline',
    'modelName': 'house-value-model',
    'startTime': '2025-02-25T16:02:28Z',
    'endTime': '2025-02-25T16:04:28Z',
    'interval': '5s'
}

response = requests.get(url, params=params)
```

{% endcode %}

<details>

<summary>Expand to see an example of metrics API response</summary>

{% code overflow="wrap" lineNumbers="true" %}

```json
{
  "metrics": [
    {
      "endTime": "2025-02-25T16:02:33Z",
      "meanAbsoluteError": 3.5947158,
      "meanSquaredError": 12.921982,
      "rootMeanSquaredError": 3.5947158
    },
    {
      "endTime": "2025-02-25T16:02:38Z",
      "meanAbsoluteError": 3.5947158,
      "meanSquaredError": 12.921982,
      "rootMeanSquaredError": 3.5947158
    },
    {
      "endTime": "2025-02-25T16:02:43Z",
      "meanAbsoluteError": 3.5947158,
      "meanSquaredError": 12.921982,
      "rootMeanSquaredError": 3.5947158
    },
    {
      "endTime": "2025-02-25T16:02:48Z",
      "meanAbsoluteError": 1.9672309,
      "meanSquaredError": 5.618767,
      "rootMeanSquaredError": 2.3703938
    },
    {
      "endTime": "2025-02-25T16:02:53Z",
      "meanAbsoluteError": 1.8137174,
      "meanSquaredError": 4.2309737,
      "rootMeanSquaredError": 2.0569332
    },
    {
      "endTime": "2025-02-25T16:02:58Z",
      "meanAbsoluteError": 1.7597755,
      "meanSquaredError": 6.0277104,
      "rootMeanSquaredError": 2.4551396
    },
    {
      "endTime": "2025-02-25T16:03:03Z",
      "meanAbsoluteError": 2.8212993,
      "meanSquaredError": 14.908364,
      "rootMeanSquaredError": 3.861135
    },
    {
      "endTime": "2025-02-25T16:03:08Z",
      "meanAbsoluteError": -1,
      "meanSquaredError": -1,
      "rootMeanSquaredError": -1
    },
    {
      "endTime": "2025-02-25T16:03:13Z",
      "meanAbsoluteError": -1,
      "meanSquaredError": -1,
      "rootMeanSquaredError": -1
    },
    {
      "endTime": "2025-02-25T16:03:18Z",
      "meanAbsoluteError": -1,
      "meanSquaredError": -1,
      "rootMeanSquaredError": -1
    },
    {
      "endTime": "2025-02-25T16:03:23Z",
      "meanAbsoluteError": -1,
      "meanSquaredError": -1,
      "rootMeanSquaredError": -1
    },
    {
      "endTime": "2025-02-25T16:03:28Z",
      "meanAbsoluteError": -1,
      "meanSquaredError": -1,
      "rootMeanSquaredError": -1
    },
    {
      "endTime": "2025-02-25T16:03:33Z",
      "meanAbsoluteError": -1,
      "meanSquaredError": -1,
      "rootMeanSquaredError": -1
    },
    {
      "endTime": "2025-02-25T16:03:38Z",
      "meanAbsoluteError": -1,
      "meanSquaredError": -1,
      "rootMeanSquaredError": -1
    },
    {
      "endTime": "2025-02-25T16:03:43Z",
      "meanAbsoluteError": -1,
      "meanSquaredError": -1,
      "rootMeanSquaredError": -1
    },
    {
      "endTime": "2025-02-25T16:03:48Z",
      "meanAbsoluteError": -1,
      "meanSquaredError": -1,
      "rootMeanSquaredError": -1
    },
    {
      "endTime": "2025-02-25T16:03:53Z",
      "meanAbsoluteError": -1,
      "meanSquaredError": -1,
      "rootMeanSquaredError": -1
    },
    {
      "endTime": "2025-02-25T16:03:58Z",
      "meanAbsoluteError": -1,
      "meanSquaredError": -1,
      "rootMeanSquaredError": -1
    },
    {
      "endTime": "2025-02-25T16:04:03Z",
      "meanAbsoluteError": -1,
      "meanSquaredError": -1,
      "rootMeanSquaredError": -1
    },
    {
      "endTime": "2025-02-25T16:04:08Z",
      "meanAbsoluteError": -1,
      "meanSquaredError": -1,
      "rootMeanSquaredError": -1
    },
    {
      "endTime": "2025-02-25T16:04:13Z",
      "meanAbsoluteError": -1,
      "meanSquaredError": -1,
      "rootMeanSquaredError": -1
    },
    {
      "endTime": "2025-02-25T16:04:18Z",
      "meanAbsoluteError": -1,
      "meanSquaredError": -1,
      "rootMeanSquaredError": -1
    },
    {
      "endTime": "2025-02-25T16:04:23Z",
      "meanAbsoluteError": -1,
      "meanSquaredError": -1,
      "rootMeanSquaredError": -1
    },
    {
      "endTime": "2025-02-25T16:04:28Z",
      "meanAbsoluteError": -1,
      "meanSquaredError": -1,
      "rootMeanSquaredError": -1
    }
  ]
}
```

{% endcode %}

</details>

Finally, we can plot the Mean Absolute Error, Mean Squared Error, Root Mean Squared Error over time.

{% code overflow="wrap" lineNumbers="true" %}

```python
%matplotlib inline

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

json_data = response.json()
df = pd.DataFrame(json_data ['metrics'])

plt.figure(figsize=(10, 6))

for metric in ['meanAbsoluteError', 'meanSquaredError', 'rootMeanSquaredError']:
    filtered = df[df[metric] != -1]
    plt.plot(filtered['endTime'], filtered[metric], label=metric)

plt.xlabel('Time')
plt.ylabel('Metrics')
plt.title('Model Performance Metrics Over Time')
plt.legend()
plt.grid(True)
plt.xticks(rotation=45)
plt.tight_layout()

plt.show()
```

{% endcode %}

![Example of a Regression Metrics plot using test data](https://4156402519-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4sbtWGU6pWCb5XK4PJN%2Fuploads%2Fgit-blob-173a96007aa83e862e68c89208bb3c0fb31bb606%2Fregression-metrics-plot.png?alt=media)
