Reading Data

ESA++ uses an indexable interface to read grid data using Python’s bracket notation. The idea is simple: pass a component type (like Bus or Gen) and optionally a field name, and you get back a pandas DataFrame. Primary-key columns are always included automatically so you can join or filter results without extra bookkeeping.

from esapp import PowerWorld
from esapp.components import *

pw = PowerWorld("path/to/case.pwb")

Importing * from esapp.components brings in all component types (Bus, Gen, Load, Branch, Shunt, Area, Zone, etc.) so they’re available directly in your code.

Read Patterns

There are four ways to read data, ranging from just the keys to every available field:

Syntax

Returns

pw[Bus]

Key columns only (e.g. BusNum)

pw[Bus, "BusPUVolt"]

Keys + one field

pw[Bus, ["BusPUVolt", "BusAngle"]]

Keys + multiple fields

pw[Bus, :]

Keys + every defined field

Let’s walk through each one.

Passing just the component type returns its primary-key columns. Buses have a single key (BusNum); generators have a compound key (BusNum, GenID).

In [2]:
pw[Bus].head()
Out[2]:
BusNum
0 1
1 2
2 3
3 4
4 5
In [3]:
pw[Gen].head()
Out[3]:
BusNum GenID
0 2 1
1 2 2
2 2 3
3 2 4
4 23 1

Adding a field name retrieves that column alongside the keys. You can pass a single string or a list of strings for multiple columns. Field names can also be specified as component enum attributes (e.g. Bus.BusPUVolt) for IDE autocomplete and typo protection.

In [4]:
pw[Bus, "BusPUVolt"].head()
Out[4]:
BusNum BusPUVolt
0 1 0.993545
1 2 0.991225
2 3 0.984548
3 4 0.978800
4 5 0.988985
In [5]:
pw[Gen, ["GenMW", "GenMVR", "GenStatus"]].head()
Out[5]:
BusNum GenID GenMVR GenMW GenStatus
0 2 1 0.80000 2.500000 Closed
1 2 2 0.80000 2.500000 Closed
2 2 3 0.80000 2.500000 Closed
3 2 4 0.80000 2.500000 Closed
4 23 1 0.04408 69.274741 Closed
In [6]:
# Same query using enum attributes instead of strings:
pw[Bus, [Bus.BusName, Bus.BusPUVolt, Bus.BusAngle]].head()
Out[6]:
BusAngle BusName BusNum BusPUVolt
0 -1.119907 ALOHA138 1 0.993545
1 -3.927372 ALOHA69 2 0.991225
2 -4.731145 FLOWER69 3 0.984548
3 -5.745870 WAVE69 4 0.978800
4 -2.069792 HONOLULU138 5 0.988985

The slice syntax pw[Bus, :] retrieves every defined field at once. This is handy for exploration, though it can produce wide DataFrames.

In [7]:
pw[Bus, :].shape
Out[7]:
(37, 581)

Working with Results

Every result is a standard pandas DataFrame, so all the usual filtering, grouping, and aggregation operations work directly. There’s no need to convert or reshape anything before using pandas tools you already know.

In [8]:
# Filter generators by status
gens = pw[Gen, ["GenMW", "GenMVR", "GenStatus"]]
online = gens[gens["GenStatus"] == "Closed"]
In [9]:
# Aggregate load totals
loads = pw[Load, ["LoadMW", "LoadMVR"]]
loads[["LoadMW", "LoadMVR"]].sum()
Out[9]:
LoadMW     1136.290004
LoadMVR       0.000000
dtype: float64