{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# I/O and Interoperability\n",
    "\n",
    "TimeDataModel is designed to sit between your domain logic and the broader Python data ecosystem. This notebook shows how to move data seamlessly between `TimeSeriesList` / `TimeSeriesTable` and pandas, numpy, polars, JSON, and CSV."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-03-04T19:11:03.479768Z",
     "iopub.status.busy": "2026-03-04T19:11:03.479510Z",
     "iopub.status.idle": "2026-03-04T19:11:03.531179Z",
     "shell.execute_reply": "2026-03-04T19:11:03.530616Z"
    }
   },
   "outputs": [],
   "source": [
    "from datetime import datetime, timedelta, timezone\n",
    "\n",
    "import numpy as np\n",
    "\n",
    "import timedatamodel as tdm\n",
    "\n",
    "base = datetime(2024, 1, 15, tzinfo=timezone.utc)\n",
    "timestamps = [base + timedelta(hours=i) for i in range(24)]\n",
    "values = [100.0 + 50 * np.sin(2 * np.pi * i / 24) for i in range(24)]\n",
    "\n",
    "ts = tdm.TimeSeriesList(\n",
    "    tdm.Frequency.PT1H,\n",
    "    timezone=\"UTC\",\n",
    "    timestamps=timestamps,\n",
    "    values=values,\n",
    "    name=\"power\",\n",
    "    unit=\"MW\",\n",
    "    description=\"Synthetic daily power curve\",\n",
    "    data_type=tdm.DataType.SIMULATION,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## NumPy\n",
    "\n",
    "`to_numpy()` returns a float64 array (None becomes NaN). Use it when you need fast vectorized computation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-03-04T19:11:03.532720Z",
     "iopub.status.busy": "2026-03-04T19:11:03.532595Z",
     "iopub.status.idle": "2026-03-04T19:11:03.535109Z",
     "shell.execute_reply": "2026-03-04T19:11:03.534575Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Type:  <class 'numpy.ndarray'>\n",
      "Shape: (24,)\n",
      "Mean:  100.00 MW\n",
      "Max:   150.00 MW\n"
     ]
    }
   ],
   "source": [
    "arr = ts.to_numpy()\n",
    "print(f\"Type:  {type(arr)}\")\n",
    "print(f\"Shape: {arr.shape}\")\n",
    "print(f\"Mean:  {arr.mean():.2f} MW\")\n",
    "print(f\"Max:   {arr.max():.2f} MW\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Pandas — export\n",
    "\n",
    "`to_pandas_dataframe()` produces a DataFrame with a DatetimeIndex."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-03-04T19:11:03.536374Z",
     "iopub.status.busy": "2026-03-04T19:11:03.536279Z",
     "iopub.status.idle": "2026-03-04T19:11:03.627606Z",
     "shell.execute_reply": "2026-03-04T19:11:03.627209Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>power</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>timestamp</th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>2024-01-15 00:00:00+00:00</th>\n",
       "      <td>100.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2024-01-15 01:00:00+00:00</th>\n",
       "      <td>112.940952</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2024-01-15 02:00:00+00:00</th>\n",
       "      <td>125.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2024-01-15 03:00:00+00:00</th>\n",
       "      <td>135.355339</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2024-01-15 04:00:00+00:00</th>\n",
       "      <td>143.301270</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                power\n",
       "timestamp                            \n",
       "2024-01-15 00:00:00+00:00  100.000000\n",
       "2024-01-15 01:00:00+00:00  112.940952\n",
       "2024-01-15 02:00:00+00:00  125.000000\n",
       "2024-01-15 03:00:00+00:00  135.355339\n",
       "2024-01-15 04:00:00+00:00  143.301270"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df = ts.to_pandas_dataframe()\n",
    "df.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Pandas — import with `from_pandas()`\n",
    "\n",
    "Create a TimeSeriesList from any pandas DataFrame that has a DatetimeIndex. Frequency and timezone are auto-inferred when possible."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-03-04T19:11:03.628582Z",
     "iopub.status.busy": "2026-03-04T19:11:03.628526Z",
     "iopub.status.idle": "2026-03-04T19:11:03.633169Z",
     "shell.execute_reply": "2026-03-04T19:11:03.632912Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<style>\n",
       ".ts-repr { font-family: monospace; font-size: 13px; max-width: 640px; display: inline-grid; }\n",
       ".ts-repr .ts-header {\n",
       "  font-weight: bold; font-size: 14px;\n",
       "  padding: 6px 10px; border-bottom: 2px solid #4a4a4a;\n",
       "  background: #f0f0f0; color: #1a1a1a;\n",
       "}\n",
       ".ts-repr .ts-meta { padding: 6px 10px; background: #fafafa; overflow: hidden; min-width: 0; }\n",
       ".ts-repr .ts-meta table { border-collapse: collapse; width: 100%; table-layout: fixed; }\n",
       ".ts-repr .ts-meta td { padding: 1px 8px 1px 0; white-space: nowrap; }\n",
       ".ts-repr .ts-meta td:first-child { color: #475569; font-weight: 600; width: 90px; }\n",
       ".ts-repr .ts-meta td:last-child { color: #1a1a1a; overflow: hidden; text-overflow: ellipsis; }\n",
       ".ts-repr .ts-data { padding: 6px 10px; }\n",
       ".ts-repr .ts-data table {\n",
       "  border-collapse: collapse; text-align: right;\n",
       "}\n",
       ".ts-repr .ts-data th {\n",
       "  text-align: right; padding: 3px 10px; border-bottom: 1px solid #ccc;\n",
       "  color: #555; font-weight: 600;\n",
       "}\n",
       ".ts-repr .ts-data th.ts-idx { text-align: left; }\n",
       ".ts-repr .ts-data td { padding: 2px 10px; }\n",
       ".ts-repr .ts-data tr:hover { background: #f5f5f5; }\n",
       ".ts-repr .ts-data td:first-child { text-align: left; color: #1e293b; }\n",
       ".ts-repr .ts-data td.ts-idx { text-align: left; color: #1e293b; }\n",
       ".ts-repr .ts-ellipsis { text-align: center !important; color: #999; }\n",
       "@media (prefers-color-scheme: dark) {\n",
       "  .ts-repr .ts-header { background: #1e293b; color: #e2e8f0; border-color: #475569; }\n",
       "  .ts-repr .ts-meta { background: #0f172a; }\n",
       "  .ts-repr .ts-meta td:first-child { color: #94a3b8; }\n",
       "  .ts-repr .ts-meta td:last-child { color: #e2e8f0; }\n",
       "  .ts-repr .ts-data th { color: #94a3b8; border-color: #334155; }\n",
       "  .ts-repr .ts-data td { color: #e2e8f0; }\n",
       "  .ts-repr .ts-data td:first-child { color: #cbd5e1; }\n",
       "  .ts-repr .ts-data td.ts-idx { color: #cbd5e1; }\n",
       "  .ts-repr .ts-data tr:hover { background: #1e293b; }\n",
       "  .ts-repr .ts-ellipsis { color: #64748b; }\n",
       "}\n",
       "</style>\n",
       "<div class=\"ts-repr\">\n",
       "<div class=\"ts-header\">TimeSeriesList</div>\n",
       "<div class=\"ts-meta\"><table>\n",
       "<tr><td>Name</td><td>load</td></tr>\n",
       "<tr><td>Length</td><td>48</td></tr>\n",
       "<tr><td>Frequency</td><td>PT1H</td></tr>\n",
       "<tr><td>Timezone</td><td>UTC (+00:00)</td></tr>\n",
       "<tr><td>Unit</td><td>MW</td></tr>\n",
       "<tr><td>Data type</td><td>OBSERVATION</td></tr>\n",
       "</table></div>\n",
       "<div class=\"ts-data\"><table>\n",
       "<tr><th class=\"ts-idx\">timestamp</th><th>load</th></tr>\n",
       "<tr><td>2024-06-01 00:00</td><td>156.094</td></tr>\n",
       "<tr><td>2024-06-01 01:00</td><td>129.2</td></tr>\n",
       "<tr><td>2024-06-01 02:00</td><td>165.009</td></tr>\n",
       "<tr><td class=\"ts-ellipsis\">&hellip;</td><td class=\"ts-ellipsis\">&hellip;</td></tr>\n",
       "<tr><td>2024-06-02 21:00</td><td>154.374</td></tr>\n",
       "<tr><td>2024-06-02 22:00</td><td>167.429</td></tr>\n",
       "<tr><td>2024-06-02 23:00</td><td>154.472</td></tr>\n",
       "</table></div>\n",
       "</div>"
      ],
      "text/plain": [
       "TimeSeriesList\n",
       "┌──────────────────────────────────┐\n",
       "│  Name:             load          │\n",
       "│  Length:           48            │\n",
       "│  Frequency:        PT1H          │\n",
       "│  Timezone:         UTC (+00:00)  │\n",
       "│  Unit:             MW            │\n",
       "│  Data type:        OBSERVATION   │\n",
       "├──────────────────────────────────┤\n",
       "│  2024-06-01 00:00  156.094       │\n",
       "│  2024-06-01 01:00    129.2       │\n",
       "│  2024-06-01 02:00  165.009       │\n",
       "│  ...                   ...       │\n",
       "│  2024-06-02 21:00  154.374       │\n",
       "│  2024-06-02 22:00  167.429       │\n",
       "│  2024-06-02 23:00  154.472       │\n",
       "└──────────────────────────────────┘"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import pandas as pd\n",
    "\n",
    "idx = pd.date_range(\"2024-06-01\", periods=48, freq=\"h\", tz=\"UTC\")\n",
    "df_external = pd.DataFrame({\"load\": np.random.default_rng(42).normal(150, 20, 48)}, index=idx)\n",
    "\n",
    "ts_from_pd = tdm.TimeSeriesList.from_pandas(df_external, unit=\"MW\", data_type=tdm.DataType.OBSERVATION)\n",
    "ts_from_pd"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Pandas — TimeSeriesTable round-trip"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-03-04T19:11:03.634358Z",
     "iopub.status.busy": "2026-03-04T19:11:03.634301Z",
     "iopub.status.idle": "2026-03-04T19:11:03.637044Z",
     "shell.execute_reply": "2026-03-04T19:11:03.636655Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Columns: ['wind', 'solar']\n",
      "Round-trip equals: True\n"
     ]
    }
   ],
   "source": [
    "rng = np.random.default_rng(42)\n",
    "table = tdm.TimeSeriesTable(\n",
    "    tdm.Frequency.PT1H,\n",
    "    timestamps=timestamps,\n",
    "    values=np.column_stack([\n",
    "        100 + rng.normal(0, 10, 24),\n",
    "        50 + rng.normal(0, 5, 24),\n",
    "    ]),\n",
    "    names=[\"wind\", \"solar\"],\n",
    "    units=[\"MW\", \"MW\"],\n",
    ")\n",
    "\n",
    "df_table = table.to_pandas_dataframe()\n",
    "print(f\"Columns: {list(df_table.columns)}\")\n",
    "\n",
    "table_back = tdm.TimeSeriesTable.from_pandas(df_table, units=[\"MW\", \"MW\"])\n",
    "print(f\"Round-trip equals: {table.equals(table_back)}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Polars (optional)\n",
    "\n",
    "If polars is installed, you get `to_polars_dataframe()` and `from_polars()`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-03-04T19:11:03.637972Z",
     "iopub.status.busy": "2026-03-04T19:11:03.637921Z",
     "iopub.status.idle": "2026-03-04T19:11:03.741679Z",
     "shell.execute_reply": "2026-03-04T19:11:03.741255Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "shape: (5, 2)\n",
      "┌─────────────────────────┬────────────┐\n",
      "│ timestamp               ┆ power      │\n",
      "│ ---                     ┆ ---        │\n",
      "│ datetime[μs, UTC]       ┆ f64        │\n",
      "╞═════════════════════════╪════════════╡\n",
      "│ 2024-01-15 00:00:00 UTC ┆ 100.0      │\n",
      "│ 2024-01-15 01:00:00 UTC ┆ 112.940952 │\n",
      "│ 2024-01-15 02:00:00 UTC ┆ 125.0      │\n",
      "│ 2024-01-15 03:00:00 UTC ┆ 135.355339 │\n",
      "│ 2024-01-15 04:00:00 UTC ┆ 143.30127  │\n",
      "└─────────────────────────┴────────────┘\n",
      "\n",
      "Round-trip length: 24\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    df_pl = ts.to_polars_dataframe()\n",
    "    print(df_pl.head())\n",
    "\n",
    "    ts_from_pl = tdm.TimeSeriesList.from_polars(df_pl, tdm.Frequency.PT1H, unit=\"MW\")\n",
    "    print(f\"\\nRound-trip length: {len(ts_from_pl)}\")\n",
    "except ImportError:\n",
    "    print(\"polars not installed — skip this cell or: pip install timedatamodel[polars]\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## JSON serialization\n",
    "\n",
    "`to_json()` produces an ISO-8601 JSON string. `from_json()` reconstructs the series with full metadata."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-03-04T19:11:03.742688Z",
     "iopub.status.busy": "2026-03-04T19:11:03.742629Z",
     "iopub.status.idle": "2026-03-04T19:11:03.744302Z",
     "shell.execute_reply": "2026-03-04T19:11:03.743943Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{\"timestamps\": [\"2024-01-15T00:00:00+00:00\", \"2024-01-15T01:00:00+00:00\", \"2024-01-15T02:00:00+00:00\", \"2024-01-15T03:00:00+00:00\", \"2024-01-15T04:00:00+00:00\", \"2024-01-15T05:00:00+00:00\", \"2024-01-1 ...\n"
     ]
    }
   ],
   "source": [
    "json_str = ts.to_json()\n",
    "print(json_str[:200], \"...\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-03-04T19:11:03.745088Z",
     "iopub.status.busy": "2026-03-04T19:11:03.745015Z",
     "iopub.status.idle": "2026-03-04T19:11:03.746772Z",
     "shell.execute_reply": "2026-03-04T19:11:03.746460Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Name:      power\n",
      "Unit:      MW\n",
      "Frequency: PT1H\n",
      "Length:    24\n",
      "Equals original: True\n"
     ]
    }
   ],
   "source": [
    "ts_restored = tdm.TimeSeriesList.from_json(json_str)\n",
    "print(f\"Name:      {ts_restored.name}\")\n",
    "print(f\"Unit:      {ts_restored.unit}\")\n",
    "print(f\"Frequency: {ts_restored.frequency}\")\n",
    "print(f\"Length:    {len(ts_restored)}\")\n",
    "print(f\"Equals original: {ts.equals(ts_restored)}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### JSON for TimeSeriesTable"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-03-04T19:11:03.747657Z",
     "iopub.status.busy": "2026-03-04T19:11:03.747601Z",
     "iopub.status.idle": "2026-03-04T19:11:03.749280Z",
     "shell.execute_reply": "2026-03-04T19:11:03.748991Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Columns: ('wind', 'solar')\n",
      "Equals original: True\n"
     ]
    }
   ],
   "source": [
    "json_table = table.to_json()\n",
    "table_restored = tdm.TimeSeriesTable.from_json(json_table)\n",
    "print(f\"Columns: {table_restored.column_names}\")\n",
    "print(f\"Equals original: {table.equals(table_restored)}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## CSV serialization\n",
    "\n",
    "`to_csv()` and `from_csv()` write and read simple CSV files."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-03-04T19:11:03.750077Z",
     "iopub.status.busy": "2026-03-04T19:11:03.750023Z",
     "iopub.status.idle": "2026-03-04T19:11:03.752342Z",
     "shell.execute_reply": "2026-03-04T19:11:03.752062Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "timestamp,power\n",
      "2024-01-15T00:00:00+00:00,100.0\n",
      "2024-01-15T01:00:00+00:00,112.94095225512604\n",
      "2024-01-15T02:00:00+00:00,125.0\n",
      "2024-01-15T03:00:00+00:00,135.35533905932738\n",
      "...\n"
     ]
    }
   ],
   "source": [
    "import tempfile\n",
    "from pathlib import Path\n",
    "\n",
    "with tempfile.TemporaryDirectory() as tmp:\n",
    "    csv_path = Path(tmp) / \"power.csv\"\n",
    "    ts.to_csv(csv_path)\n",
    "\n",
    "    with open(csv_path) as f:\n",
    "        for i, line in enumerate(f):\n",
    "            print(line.rstrip())\n",
    "            if i >= 4:\n",
    "                print(\"...\")\n",
    "                break"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-03-04T19:11:03.753292Z",
     "iopub.status.busy": "2026-03-04T19:11:03.753231Z",
     "iopub.status.idle": "2026-03-04T19:11:03.755850Z",
     "shell.execute_reply": "2026-03-04T19:11:03.755584Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Length: 24\n",
      "Name:   power\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<style>\n",
       ".ts-repr { font-family: monospace; font-size: 13px; max-width: 640px; display: inline-grid; }\n",
       ".ts-repr .ts-header {\n",
       "  font-weight: bold; font-size: 14px;\n",
       "  padding: 6px 10px; border-bottom: 2px solid #4a4a4a;\n",
       "  background: #f0f0f0; color: #1a1a1a;\n",
       "}\n",
       ".ts-repr .ts-meta { padding: 6px 10px; background: #fafafa; overflow: hidden; min-width: 0; }\n",
       ".ts-repr .ts-meta table { border-collapse: collapse; width: 100%; table-layout: fixed; }\n",
       ".ts-repr .ts-meta td { padding: 1px 8px 1px 0; white-space: nowrap; }\n",
       ".ts-repr .ts-meta td:first-child { color: #475569; font-weight: 600; width: 90px; }\n",
       ".ts-repr .ts-meta td:last-child { color: #1a1a1a; overflow: hidden; text-overflow: ellipsis; }\n",
       ".ts-repr .ts-data { padding: 6px 10px; }\n",
       ".ts-repr .ts-data table {\n",
       "  border-collapse: collapse; text-align: right;\n",
       "}\n",
       ".ts-repr .ts-data th {\n",
       "  text-align: right; padding: 3px 10px; border-bottom: 1px solid #ccc;\n",
       "  color: #555; font-weight: 600;\n",
       "}\n",
       ".ts-repr .ts-data th.ts-idx { text-align: left; }\n",
       ".ts-repr .ts-data td { padding: 2px 10px; }\n",
       ".ts-repr .ts-data tr:hover { background: #f5f5f5; }\n",
       ".ts-repr .ts-data td:first-child { text-align: left; color: #1e293b; }\n",
       ".ts-repr .ts-data td.ts-idx { text-align: left; color: #1e293b; }\n",
       ".ts-repr .ts-ellipsis { text-align: center !important; color: #999; }\n",
       "@media (prefers-color-scheme: dark) {\n",
       "  .ts-repr .ts-header { background: #1e293b; color: #e2e8f0; border-color: #475569; }\n",
       "  .ts-repr .ts-meta { background: #0f172a; }\n",
       "  .ts-repr .ts-meta td:first-child { color: #94a3b8; }\n",
       "  .ts-repr .ts-meta td:last-child { color: #e2e8f0; }\n",
       "  .ts-repr .ts-data th { color: #94a3b8; border-color: #334155; }\n",
       "  .ts-repr .ts-data td { color: #e2e8f0; }\n",
       "  .ts-repr .ts-data td:first-child { color: #cbd5e1; }\n",
       "  .ts-repr .ts-data td.ts-idx { color: #cbd5e1; }\n",
       "  .ts-repr .ts-data tr:hover { background: #1e293b; }\n",
       "  .ts-repr .ts-ellipsis { color: #64748b; }\n",
       "}\n",
       "</style>\n",
       "<div class=\"ts-repr\">\n",
       "<div class=\"ts-header\">TimeSeriesList</div>\n",
       "<div class=\"ts-meta\"><table>\n",
       "<tr><td>Name</td><td>power</td></tr>\n",
       "<tr><td>Length</td><td>5</td></tr>\n",
       "<tr><td>Frequency</td><td>PT1H</td></tr>\n",
       "<tr><td>Timezone</td><td>UTC (+00:00)</td></tr>\n",
       "<tr><td>Unit</td><td>MW</td></tr>\n",
       "</table></div>\n",
       "<div class=\"ts-data\"><table>\n",
       "<tr><th class=\"ts-idx\">timestamp</th><th>power</th></tr>\n",
       "<tr><td>2024-01-15 00:00</td><td>100.0</td></tr>\n",
       "<tr><td>2024-01-15 01:00</td><td>112.941</td></tr>\n",
       "<tr><td>2024-01-15 02:00</td><td>125.0</td></tr>\n",
       "</table></div>\n",
       "</div>"
      ],
      "text/plain": [
       "TimeSeriesList\n",
       "┌──────────────────────────────────┐\n",
       "│  Name:             power         │\n",
       "│  Length:           5             │\n",
       "│  Frequency:        PT1H          │\n",
       "│  Timezone:         UTC (+00:00)  │\n",
       "│  Unit:             MW            │\n",
       "├──────────────────────────────────┤\n",
       "│  2024-01-15 00:00    100.0       │\n",
       "│  2024-01-15 01:00  112.941       │\n",
       "│  2024-01-15 02:00    125.0       │\n",
       "│  2024-01-15 03:00  135.355       │\n",
       "│  2024-01-15 04:00  143.301       │\n",
       "└──────────────────────────────────┘"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "with tempfile.TemporaryDirectory() as tmp:\n",
    "    csv_path = Path(tmp) / \"power.csv\"\n",
    "    ts.to_csv(csv_path)\n",
    "    ts_from_csv = tdm.TimeSeriesList.from_csv(csv_path, tdm.Frequency.PT1H, unit=\"MW\")\n",
    "\n",
    "print(f\"Length: {len(ts_from_csv)}\")\n",
    "print(f\"Name:   {ts_from_csv.name}\")\n",
    "ts_from_csv.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Multi-index round-trip\n",
    "\n",
    "TimeSeriesList supports tuple-based timestamps for hierarchical indexing. This is preserved through pandas and JSON round-trips."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-03-04T19:11:03.756751Z",
     "iopub.status.busy": "2026-03-04T19:11:03.756701Z",
     "iopub.status.idle": "2026-03-04T19:11:03.760636Z",
     "shell.execute_reply": "2026-03-04T19:11:03.760282Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th>forecast</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>issue_time</th>\n",
       "      <th>valid_time</th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th rowspan=\"3\" valign=\"top\">2024-01-15 00:00:00+00:00</th>\n",
       "      <th>2024-01-15 00:00:00+00:00</th>\n",
       "      <td>100.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2024-01-15 01:00:00+00:00</th>\n",
       "      <td>105.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2024-01-15 02:00:00+00:00</th>\n",
       "      <td>110.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th rowspan=\"3\" valign=\"top\">2024-01-15 12:00:00+00:00</th>\n",
       "      <th>2024-01-15 12:00:00+00:00</th>\n",
       "      <td>95.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2024-01-15 13:00:00+00:00</th>\n",
       "      <td>100.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2024-01-15 14:00:00+00:00</th>\n",
       "      <td>108.0</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                                     forecast\n",
       "issue_time                valid_time                         \n",
       "2024-01-15 00:00:00+00:00 2024-01-15 00:00:00+00:00     100.0\n",
       "                          2024-01-15 01:00:00+00:00     105.0\n",
       "                          2024-01-15 02:00:00+00:00     110.0\n",
       "2024-01-15 12:00:00+00:00 2024-01-15 12:00:00+00:00      95.0\n",
       "                          2024-01-15 13:00:00+00:00     100.0\n",
       "                          2024-01-15 14:00:00+00:00     108.0"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "issue_times = [\n",
    "    datetime(2024, 1, 15, 0, tzinfo=timezone.utc),\n",
    "    datetime(2024, 1, 15, 0, tzinfo=timezone.utc),\n",
    "    datetime(2024, 1, 15, 0, tzinfo=timezone.utc),\n",
    "    datetime(2024, 1, 15, 12, tzinfo=timezone.utc),\n",
    "    datetime(2024, 1, 15, 12, tzinfo=timezone.utc),\n",
    "    datetime(2024, 1, 15, 12, tzinfo=timezone.utc),\n",
    "]\n",
    "valid_times = [\n",
    "    datetime(2024, 1, 15, 0, tzinfo=timezone.utc),\n",
    "    datetime(2024, 1, 15, 1, tzinfo=timezone.utc),\n",
    "    datetime(2024, 1, 15, 2, tzinfo=timezone.utc),\n",
    "    datetime(2024, 1, 15, 12, tzinfo=timezone.utc),\n",
    "    datetime(2024, 1, 15, 13, tzinfo=timezone.utc),\n",
    "    datetime(2024, 1, 15, 14, tzinfo=timezone.utc),\n",
    "]\n",
    "\n",
    "ts_multi = tdm.TimeSeriesList(\n",
    "    tdm.Frequency.PT1H,\n",
    "    timestamps=list(zip(issue_times, valid_times)),\n",
    "    values=[100.0, 105.0, 110.0, 95.0, 100.0, 108.0],\n",
    "    name=\"forecast\",\n",
    "    unit=\"MW\",\n",
    "    index_names=[\"issue_time\", \"valid_time\"],\n",
    ")\n",
    "\n",
    "df_multi = ts_multi.to_pandas_dataframe()\n",
    "df_multi"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-03-04T19:11:03.761470Z",
     "iopub.status.busy": "2026-03-04T19:11:03.761416Z",
     "iopub.status.idle": "2026-03-04T19:11:03.763225Z",
     "shell.execute_reply": "2026-03-04T19:11:03.762901Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Index names: ('issue_time', 'valid_time')\n",
      "Multi-index: True\n",
      "Equals original: True\n"
     ]
    }
   ],
   "source": [
    "ts_multi_back = tdm.TimeSeriesList.from_pandas(df_multi, tdm.Frequency.PT1H, unit=\"MW\")\n",
    "print(f\"Index names: {ts_multi_back.index_names}\")\n",
    "print(f\"Multi-index: {ts_multi_back.is_multi_index}\")\n",
    "print(f\"Equals original: {ts_multi.equals(ts_multi_back)}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Summary\n",
    "\n",
    "| Format | Export | Import | Metadata preserved |\n",
    "|--------|--------|--------|-------------------|\n",
    "| NumPy | `to_numpy()` | — | Values only |\n",
    "| pandas | `to_pandas_dataframe()` | `from_pandas()` | Name, freq, tz |\n",
    "| polars | `to_polars_dataframe()` | `from_polars()` | Name |\n",
    "| JSON | `to_json()` | `from_json()` | Full |\n",
    "| CSV | `to_csv()` | `from_csv()` | Timestamps + values |\n",
    "\n",
    "Next up: **nb_09** covers geographical support — GeoLocation, GeoArea, and spatial queries."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.14.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
