> For the complete documentation index, see [llms.txt](https://developer.abacum.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developer.abacum.ai/guides/public-api-guides/analyze-model-data.md).

# Analyze model data

Pull model data into a pandas table or a Google Sheet.

These recipes pull the actuals of one model for one year, then put the numbers where you analyze them. They use the steps in [Export model data](/guides/public-api-guides/export-model-data.md). You need a user with the Admin role, and the ID of a model from `GET /public-api/models`.

The data endpoints return a download link, not the rows. Each recipe calls the endpoint, then downloads the file. See [Download links](/essentials/concepts/download-links.md).

{% tabs %}
{% tab title="Python (pandas)" %}
This recipe builds one column for each variable and one row for each month. It reads the variable names from the `metadata` of the response, so it does not depend on the order of the columns. See [Data file format](/essentials/concepts/data-file-format.md).

```python
import io

import pandas as pd
import requests

BASE_URL = "https://api.abacum.io"
MODEL_ID = "MODEL_ID"  # an id from GET /public-api/models
headers = {"Authorization": "Bearer YOUR_ACCESS_TOKEN"}

response = requests.get(
    f"{BASE_URL}/public-api/models/{MODEL_ID}/actuals_data",
    headers=headers,
    params={"start_date": "2024-01", "end_date": "2024-12"},
    timeout=60,
)
response.raise_for_status()
body = response.json()

file = requests.get(body["data"], timeout=120)
file.raise_for_status()
frame = pd.read_csv(io.BytesIO(file.content), parse_dates=["time"])

variables = body["metadata"]["properties"]["variable_id"]["variables"]
frame["variable"] = frame["variable_id"].map({key: value["name"] for key, value in variables.items()})

monthly = frame.pivot_table(index="time", columns="variable", values="value", aggfunc="sum")
print(monthly)
```

The `value` column holds one row for each combination of dimension items. The pivot adds them up. To keep a dimension, add its column to `index`.

For a large model, send `output_format=parquet` and read the file with `pd.read_parquet`.

To handle `429` and the other errors, use the helper in [Handle errors and retries](/guides/handle-errors-and-retries.md).
{% endtab %}

{% tab title="Google Sheets" %}
This script writes the same file into a sheet called `Abacum actuals`. Run it from the script editor, or add a time-based trigger to refresh it each day.

1. In the sheet, open **Extensions** > **Apps Script**, and paste the script.
2. Open **Project settings**, and add these script properties: `CLIENT_ID`, `CLIENT_SECRET` and `MODEL_ID`. Do not put the secret in the code.

```javascript
function importActuals() {
  const baseUrl = "https://api.abacum.io";
  const properties = PropertiesService.getScriptProperties();

  const tokenResponse = UrlFetchApp.fetch(baseUrl + "/server-authentication/oauth2/token/", {
    method: "post",
    payload: {
      grant_type: "client_credentials",
      client_id: properties.getProperty("CLIENT_ID"),
      client_secret: properties.getProperty("CLIENT_SECRET"),
    },
  });
  const token = JSON.parse(tokenResponse.getContentText()).access_token;

  const modelId = properties.getProperty("MODEL_ID");
  const dataResponse = UrlFetchApp.fetch(
    baseUrl + "/public-api/models/" + modelId + "/actuals_data?start_date=2024-01&end_date=2024-12",
    { headers: { Authorization: "Bearer " + token } }
  );
  const downloadUrl = JSON.parse(dataResponse.getContentText()).data;

  const rows = Utilities.parseCsv(UrlFetchApp.fetch(downloadUrl).getContentText());
  const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = spreadsheet.getSheetByName("Abacum actuals") || spreadsheet.insertSheet("Abacum actuals");
  sheet.clear();
  sheet.getRange(1, 1, rows.length, rows[0].length).setValues(rows);
}
```

The sheet holds the raw rows: `variable_id`, `time`, `value`, and one column for each dimension. Use a pivot table to summarize them. Google limits how long a script can run and how many cells a sheet can hold, so ask for one period at a time with `start_date` and `end_date`. See [Filter by period](/essentials/concepts/filter-by-period.md).
{% endtab %}
{% endtabs %}
