Spaces:
Runtime error
Runtime error
Sync from GitHub via hub-sync
Browse files- .gitattributes +1 -0
- Dockerfile +0 -20
- README.md +91 -14
- data/__init__.py +0 -0
- data/app.py +134 -0
- data/electricity.data +3 -0
- data/electricity.py +134 -0
- images/energy_usage_ny_sankey_chart.svg +1 -0
- images/energy_usage_us_sankey_chart.svg +1 -0
- images/key_metrics_corr_heatmap.svg +1 -0
- images/rate_disparity_dumbbell_plot.svg +1 -0
- images/rate_fairness_dual_y_scatter_plot.svg +1 -0
- images/top_ten_state_res_variance_table.svg +1 -0
- images/utility_type_strip_plot.svg +1 -0
- requirements.txt +4 -2
- src/__init__.py +0 -0
- src/streamlit_app.py +0 -40
- src/util/__init__.py +0 -0
- src/util/data_util.py +129 -0
- src/util/plot_util.py +322 -0
- streamlit_app.py +121 -0
- utility_efficiency_fairness.ipynb +1622 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
data/electricity.data filter=lfs diff=lfs merge=lfs -text
|
Dockerfile
DELETED
|
@@ -1,20 +0,0 @@
|
|
| 1 |
-
FROM python:3.13.5-slim
|
| 2 |
-
|
| 3 |
-
WORKDIR /app
|
| 4 |
-
|
| 5 |
-
RUN apt-get update && apt-get install -y \
|
| 6 |
-
build-essential \
|
| 7 |
-
curl \
|
| 8 |
-
git \
|
| 9 |
-
&& rm -rf /var/lib/apt/lists/*
|
| 10 |
-
|
| 11 |
-
COPY requirements.txt ./
|
| 12 |
-
COPY src/ ./src/
|
| 13 |
-
|
| 14 |
-
RUN pip3 install -r requirements.txt
|
| 15 |
-
|
| 16 |
-
EXPOSE 8501
|
| 17 |
-
|
| 18 |
-
HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
|
| 19 |
-
|
| 20 |
-
ENTRYPOINT ["streamlit", "run", "src/streamlit_app.py", "--server.port=8501", "--server.address=0.0.0.0"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
CHANGED
|
@@ -1,19 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
---
|
| 13 |
|
| 14 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
-
|
| 17 |
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
| 1 |
+
# Electricity Utility Inefficiency & Residential Rate Analysis
|
| 2 |
+
|
| 3 |
+
> **Research Question:** Are system-level inefficiencies — high energy losses and poor load factors — statistically correlated with higher retail rates for residential consumers?
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## Overview
|
| 8 |
+
|
| 9 |
+
This project investigates a fundamental fairness question in the U.S. electricity sector: do residential customers end up paying more when their utility operates inefficiently? Using the [CORGIS Electricity Dataset](https://corgis-edu.github.io/corgis/), this analysis builds a set of derived efficiency and equity metrics, then examines their statistical relationships through a suite of interactive visualizations.
|
| 10 |
+
|
| 11 |
+
New York State serves as the primary case study. NY was selected through a data-driven ranking process (get_state_variance) that scores all 50 states across five analytical criteria: number of utilities, number of ownership types, residential price standard deviation, maximum system loss percentage, and industrial revenue dependency. New York ranks at or near the top on all five: it has over 100 utilities across 6 distinct ownership models, exhibits high residential price variance, and sits within one of the most actively scrutinized regulatory environments in the U.S.
|
| 12 |
+
|
| 13 |
+
[](https://huggingface.co/spaces/chalseee/utilities-equity-efficiency-gap)
|
| 14 |
---
|
| 15 |
+
|
| 16 |
+
## Key Metrics
|
| 17 |
+
|
| 18 |
+
| Metric | Description |
|
| 19 |
+
|---|---|
|
| 20 |
+
| `System Loss Percentage` | Energy lost in transmission/distribution as % of total supply |
|
| 21 |
+
| `Load Factor` | Ratio of actual energy delivered to theoretical maximum (demand efficiency) |
|
| 22 |
+
| `Residential Unit Price` | Residential rate in $/MWh |
|
| 23 |
+
| `Industrial Unit Price` | Industrial rate in $/MWh |
|
| 24 |
+
| `Price Spread` | Gap between residential and industrial rates (equity indicator) |
|
| 25 |
---
|
| 26 |
|
| 27 |
+
## Visualizations
|
| 28 |
+
|
| 29 |
+
- **State Selection Table** — Ranking of top 10 states by analytical richness; justifies NY case study
|
| 30 |
+
- **Price Spread Strip Plot** — Residential premium over industrial rates by ownership model
|
| 31 |
+
- **Correlation Heatmap** — Statistical significance matrix across all key metrics
|
| 32 |
+
- **Fairness Audit Scatter** — System loss (%) and load factor (dual y-axis) vs. residential price by ownership model, with OLS trendline
|
| 33 |
+
- **Rate Disparity Dumbbell** — Top 10 utilities by residential–industrial price gap
|
| 34 |
+
- **Energy Flow Sankey Diagram (State)** — Per-state (or utility) breakdown of energy sources and uses as percentages
|
| 35 |
+
- **Energy Flow Sankey Diagram (US)** — National aggregate energy flow
|
| 36 |
+
|
| 37 |
+
---
|
| 38 |
+
|
| 39 |
+
## Project Structure
|
| 40 |
+
|
| 41 |
+
```
|
| 42 |
+
.
|
| 43 |
+
├── utility_efficiency_fairness.ipynb
|
| 44 |
+
├── streamlit_app.py
|
| 45 |
+
├── requirements.txt
|
| 46 |
+
├── README.md
|
| 47 |
+
├── data/
|
| 48 |
+
│ └── app.py
|
| 49 |
+
│ └── electricity.data
|
| 50 |
+
│ └── electricity.data
|
| 51 |
+
├── src/
|
| 52 |
+
│ └── util/
|
| 53 |
+
│ ├── data_util.py
|
| 54 |
+
│ └── plot_util.py
|
| 55 |
+
└── images/
|
| 56 |
+
```
|
| 57 |
+
|
| 58 |
+
The three modules work as a clean pipeline:
|
| 59 |
+
- **`electricity`** — CORGIS data loader (unmodified third-party)
|
| 60 |
+
- **`data_util`** — all data preparation, filtering, and metric engineering
|
| 61 |
+
- **`plot_util`** — all chart construction and SVG expor
|
| 62 |
+
|
| 63 |
+
### `util.py` — Helper Functions
|
| 64 |
+
|
| 65 |
+
| Function | Purpose |
|
| 66 |
+
|---|---|
|
| 67 |
+
| `prepare_data(df)` | Calculates key metrics and other features from raw columns |
|
| 68 |
+
| `get_state_data(state, df)` | Filters and subsets data for a given state |
|
| 69 |
+
| `get_state_variance(df)` | Ranks states by residential price variance (for state selection) |
|
| 70 |
+
| `get_customer_utilities(df, customer)` | Filters utilities by customer type served |
|
| 71 |
+
| `get_residential_load_factor(df)` | Subset with valid load factor for residential utilities |
|
| 72 |
+
| `get_residential_sys_loss(df)` | Subset with valid loss % for residential utilities |
|
| 73 |
+
| `get_utility_usage(utility)` | Converts raw MWh values to % of total supply for Sankey |
|
| 74 |
+
|
| 75 |
+
---
|
| 76 |
+
|
| 77 |
+
## Setup & Usage
|
| 78 |
+
|
| 79 |
+
- The dataset is pre-bundled — no external downloads required for it.
|
| 80 |
+
- Run `jupytr notebook utility_efficiency_fairness.ipynb.`
|
| 81 |
+
- Run pip install pandas plotly scipy kaleido streamlit
|
| 82 |
+
|
| 83 |
+
---
|
| 84 |
+
|
| 85 |
+
## Data Source
|
| 86 |
+
|
| 87 |
+
**[CORGIS Electricity Dataset](https://corgis-edu.github.io/corgis/python/electricity/)** — a cleaned and structured snapshot of U.S. Energy Information Administration (EIA) [Form 861](https://www.eia.gov/electricity/data/eia861/) data, covering utility-level electricity generation, sales, revenues, and customer counts across all U.S. states.
|
| 88 |
+
|
| 89 |
+
---
|
| 90 |
|
| 91 |
+
## Potential Extensions
|
| 92 |
|
| 93 |
+
- Expand the analysis to all 50 states and compare regional patterns
|
| 94 |
+
- Incorporate time-series data to track changes in efficiency and pricing
|
| 95 |
+
- Apply regression modeling to control for utility size and customer density
|
| 96 |
+
- Explore the role of renewable energy mix on system loss rates
|
data/__init__.py
ADDED
|
File without changes
|
data/app.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'''
|
| 2 |
+
Hello! Thank you for downloading a CORGIS library. However, you do not
|
| 3 |
+
need to open this file. Instead you should make your own Python file and
|
| 4 |
+
add the following line:
|
| 5 |
+
|
| 6 |
+
import electricity
|
| 7 |
+
|
| 8 |
+
Then just place the files you downloaded alongside it.
|
| 9 |
+
'''
|
| 10 |
+
|
| 11 |
+
import os as _os
|
| 12 |
+
import pickle as _pickle
|
| 13 |
+
|
| 14 |
+
__all__ = ['get_utility']
|
| 15 |
+
|
| 16 |
+
def _tifa_definitions():
|
| 17 |
+
return {"type": "ModuleType",
|
| 18 |
+
"fields": {
|
| 19 |
+
'get': {
|
| 20 |
+
"type": "FunctionType",
|
| 21 |
+
"name": 'get',
|
| 22 |
+
"returns": {
|
| 23 |
+
"type": "ListType",
|
| 24 |
+
"empty": False,
|
| 25 |
+
"subtype": {"type": "NumType"}
|
| 26 |
+
}
|
| 27 |
+
},
|
| 28 |
+
'get_utility': {
|
| 29 |
+
"type": "FunctionType",
|
| 30 |
+
"name": 'get_utility',
|
| 31 |
+
"returns":
|
| 32 |
+
{"type": "ListType", "subtype":
|
| 33 |
+
{"type": "DictType", "literals": [{"type": "LiteralStr", "value": 'Utility'}, {"type": "LiteralStr", "value": 'Demand'}, {"type": "LiteralStr", "value": 'Sources'}, {"type": "LiteralStr", "value": 'Uses'}, {"type": "LiteralStr", "value": 'Revenues'}, {"type": "LiteralStr", "value": 'Revenue'}, {"type": "LiteralStr", "value": 'Retail'}], "values": [
|
| 34 |
+
{"type": "DictType", "literals": [{"type": "LiteralStr", "value": 'Number'}, {"type": "LiteralStr", "value": 'Name'}, {"type": "LiteralStr", "value": 'State'}, {"type": "LiteralStr", "value": 'Type'}], "values": [
|
| 35 |
+
{"type": "NumType"},
|
| 36 |
+
{"type": "StrType"},
|
| 37 |
+
{"type": "StrType"},
|
| 38 |
+
{"type": "StrType"}]},
|
| 39 |
+
{"type": "DictType", "literals": [{"type": "LiteralStr", "value": 'Summer Peak'}, {"type": "LiteralStr", "value": 'Winter Peak'}], "values": [
|
| 40 |
+
{"type": "NumType"},
|
| 41 |
+
{"type": "NumType"}]},
|
| 42 |
+
{"type": "DictType", "literals": [{"type": "LiteralStr", "value": 'Generation'}, {"type": "LiteralStr", "value": 'Purchased'}, {"type": "LiteralStr", "value": 'Other'}, {"type": "LiteralStr", "value": 'Total'}], "values": [
|
| 43 |
+
{"type": "NumType"},
|
| 44 |
+
{"type": "NumType"},
|
| 45 |
+
{"type": "NumType"},
|
| 46 |
+
{"type": "NumType"}]},
|
| 47 |
+
{"type": "DictType", "literals": [{"type": "LiteralStr", "value": 'Retail'}, {"type": "LiteralStr", "value": 'Resale'}, {"type": "LiteralStr", "value": 'No Charge'}, {"type": "LiteralStr", "value": 'Consumed'}, {"type": "LiteralStr", "value": 'Losses'}, {"type": "LiteralStr", "value": 'Total'}], "values": [
|
| 48 |
+
{"type": "NumType"},
|
| 49 |
+
{"type": "NumType"},
|
| 50 |
+
{"type": "NumType"},
|
| 51 |
+
{"type": "NumType"},
|
| 52 |
+
{"type": "NumType"},
|
| 53 |
+
{"type": "NumType"}]},
|
| 54 |
+
{"type": "DictType", "literals": [{"type": "LiteralStr", "value": 'Retail'}], "values": [
|
| 55 |
+
{"type": "NumType"}]},
|
| 56 |
+
{"type": "DictType", "literals": [{"type": "LiteralStr", "value": 'Delivery'}, {"type": "LiteralStr", "value": 'Resale'}, {"type": "LiteralStr", "value": 'Adjustments'}, {"type": "LiteralStr", "value": 'Transmission'}, {"type": "LiteralStr", "value": 'Other'}, {"type": "LiteralStr", "value": 'Total'}], "values": [
|
| 57 |
+
{"type": "NumType"},
|
| 58 |
+
{"type": "NumType"},
|
| 59 |
+
{"type": "NumType"},
|
| 60 |
+
{"type": "NumType"},
|
| 61 |
+
{"type": "NumType"},
|
| 62 |
+
{"type": "NumType"}]},
|
| 63 |
+
{"type": "DictType", "literals": [{"type": "LiteralStr", "value": 'Residential'}, {"type": "LiteralStr", "value": 'Commercial'}, {"type": "LiteralStr", "value": 'Industrial'}, {"type": "LiteralStr", "value": 'Transportation'}, {"type": "LiteralStr", "value": 'Total'}], "values": [
|
| 64 |
+
{"type": "DictType", "literals": [{"type": "LiteralStr", "value": 'Revenue'}, {"type": "LiteralStr", "value": 'Sales'}, {"type": "LiteralStr", "value": 'Customers'}], "values": [
|
| 65 |
+
{"type": "NumType"},
|
| 66 |
+
{"type": "NumType"},
|
| 67 |
+
{"type": "NumType"}]},
|
| 68 |
+
{"type": "DictType", "literals": [{"type": "LiteralStr", "value": 'Revenue'}, {"type": "LiteralStr", "value": 'Sales'}, {"type": "LiteralStr", "value": 'Customers'}], "values": [
|
| 69 |
+
{"type": "NumType"},
|
| 70 |
+
{"type": "NumType"},
|
| 71 |
+
{"type": "NumType"}]},
|
| 72 |
+
{"type": "DictType", "literals": [{"type": "LiteralStr", "value": 'Revenue'}, {"type": "LiteralStr", "value": 'Sales'}, {"type": "LiteralStr", "value": 'Customers'}], "values": [
|
| 73 |
+
{"type": "NumType"},
|
| 74 |
+
{"type": "NumType"},
|
| 75 |
+
{"type": "NumType"}]},
|
| 76 |
+
{"type": "DictType", "literals": [{"type": "LiteralStr", "value": 'Revenue'}, {"type": "LiteralStr", "value": 'Sales'}, {"type": "LiteralStr", "value": 'Customers'}], "values": [
|
| 77 |
+
{"type": "NumType"},
|
| 78 |
+
{"type": "NumType"},
|
| 79 |
+
{"type": "NumType"}]},
|
| 80 |
+
{"type": "DictType", "literals": [{"type": "LiteralStr", "value": 'Revenue'}, {"type": "LiteralStr", "value": 'Sales'}, {"type": "LiteralStr", "value": 'Customers'}], "values": [
|
| 81 |
+
{"type": "NumType"},
|
| 82 |
+
{"type": "NumType"},
|
| 83 |
+
{"type": "NumType"}]}]}]}}
|
| 84 |
+
},
|
| 85 |
+
}
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
class _Constants(object):
|
| 89 |
+
'''
|
| 90 |
+
Global singleton object to hide some of the constants; some IDEs reveal
|
| 91 |
+
internal module details very aggressively, and there's no other way
|
| 92 |
+
to hide stuff.
|
| 93 |
+
'''
|
| 94 |
+
|
| 95 |
+
class DatasetException(Exception):
|
| 96 |
+
''' Thrown when there is an error loading the dataset for some reason.'''
|
| 97 |
+
|
| 98 |
+
_Constants._DATABASE_NAME = _os.path.join(_os.path.dirname(__file__),
|
| 99 |
+
"electricity.data")
|
| 100 |
+
if not _os.access(_Constants._DATABASE_NAME, _os.F_OK):
|
| 101 |
+
raise DatasetException(("Error! Could not find a \"{0}\" file. "
|
| 102 |
+
"Make sure that there is a \"{0}\" in the "
|
| 103 |
+
"same directory as \"{1}.py\"! Spelling is "
|
| 104 |
+
"very important here."
|
| 105 |
+
).format(_Constants._DATABASE_NAME, __name__))
|
| 106 |
+
elif not _os.access(_Constants._DATABASE_NAME, _os.R_OK):
|
| 107 |
+
raise DatasetException(("Error! Could not read the \"{0}\" file. "
|
| 108 |
+
"Make sure that it readable by changing its "
|
| 109 |
+
"permissions. You may need to get help from "
|
| 110 |
+
"your instructor."
|
| 111 |
+
).format(_Constants._DATABASE_NAME, __name__))
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
_Constants._DATASET = None
|
| 115 |
+
|
| 116 |
+
def get_utility():
|
| 117 |
+
"""
|
| 118 |
+
Retrieves all of the utility.
|
| 119 |
+
"""
|
| 120 |
+
if _Constants._DATASET is None:
|
| 121 |
+
with open(_Constants._DATABASE_NAME, 'rb') as _:
|
| 122 |
+
_Constants._DATASET = _pickle.load(_)
|
| 123 |
+
return _Constants._DATASET
|
| 124 |
+
|
| 125 |
+
if __name__ == '__main__':
|
| 126 |
+
from pprint import pprint as _pprint
|
| 127 |
+
from timeit import default_timer as _default_timer
|
| 128 |
+
|
| 129 |
+
print(">>> get_utility()")
|
| 130 |
+
|
| 131 |
+
start_time = _default_timer()
|
| 132 |
+
result = get_utility()
|
| 133 |
+
print("Time taken: {}".format(_default_timer() - start_time))
|
| 134 |
+
_pprint(result[0])
|
data/electricity.data
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:2073f807dca20eeac617d9ea9fb7be8b4f9eb8203f48afeb408b269bd472b948
|
| 3 |
+
size 4261662
|
data/electricity.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'''
|
| 2 |
+
Hello! Thank you for downloading a CORGIS library. However, you do not
|
| 3 |
+
need to open this file. Instead you should make your own Python file and
|
| 4 |
+
add the following line:
|
| 5 |
+
|
| 6 |
+
import electricity
|
| 7 |
+
|
| 8 |
+
Then just place the files you downloaded alongside it.
|
| 9 |
+
'''
|
| 10 |
+
|
| 11 |
+
import os as _os
|
| 12 |
+
import pickle as _pickle
|
| 13 |
+
|
| 14 |
+
__all__ = ['get_utility']
|
| 15 |
+
|
| 16 |
+
def _tifa_definitions():
|
| 17 |
+
return {"type": "ModuleType",
|
| 18 |
+
"fields": {
|
| 19 |
+
'get': {
|
| 20 |
+
"type": "FunctionType",
|
| 21 |
+
"name": 'get',
|
| 22 |
+
"returns": {
|
| 23 |
+
"type": "ListType",
|
| 24 |
+
"empty": False,
|
| 25 |
+
"subtype": {"type": "NumType"}
|
| 26 |
+
}
|
| 27 |
+
},
|
| 28 |
+
'get_utility': {
|
| 29 |
+
"type": "FunctionType",
|
| 30 |
+
"name": 'get_utility',
|
| 31 |
+
"returns":
|
| 32 |
+
{"type": "ListType", "subtype":
|
| 33 |
+
{"type": "DictType", "literals": [{"type": "LiteralStr", "value": 'Utility'}, {"type": "LiteralStr", "value": 'Demand'}, {"type": "LiteralStr", "value": 'Sources'}, {"type": "LiteralStr", "value": 'Uses'}, {"type": "LiteralStr", "value": 'Revenues'}, {"type": "LiteralStr", "value": 'Revenue'}, {"type": "LiteralStr", "value": 'Retail'}], "values": [
|
| 34 |
+
{"type": "DictType", "literals": [{"type": "LiteralStr", "value": 'Number'}, {"type": "LiteralStr", "value": 'Name'}, {"type": "LiteralStr", "value": 'State'}, {"type": "LiteralStr", "value": 'Type'}], "values": [
|
| 35 |
+
{"type": "NumType"},
|
| 36 |
+
{"type": "StrType"},
|
| 37 |
+
{"type": "StrType"},
|
| 38 |
+
{"type": "StrType"}]},
|
| 39 |
+
{"type": "DictType", "literals": [{"type": "LiteralStr", "value": 'Summer Peak'}, {"type": "LiteralStr", "value": 'Winter Peak'}], "values": [
|
| 40 |
+
{"type": "NumType"},
|
| 41 |
+
{"type": "NumType"}]},
|
| 42 |
+
{"type": "DictType", "literals": [{"type": "LiteralStr", "value": 'Generation'}, {"type": "LiteralStr", "value": 'Purchased'}, {"type": "LiteralStr", "value": 'Other'}, {"type": "LiteralStr", "value": 'Total'}], "values": [
|
| 43 |
+
{"type": "NumType"},
|
| 44 |
+
{"type": "NumType"},
|
| 45 |
+
{"type": "NumType"},
|
| 46 |
+
{"type": "NumType"}]},
|
| 47 |
+
{"type": "DictType", "literals": [{"type": "LiteralStr", "value": 'Retail'}, {"type": "LiteralStr", "value": 'Resale'}, {"type": "LiteralStr", "value": 'No Charge'}, {"type": "LiteralStr", "value": 'Consumed'}, {"type": "LiteralStr", "value": 'Losses'}, {"type": "LiteralStr", "value": 'Total'}], "values": [
|
| 48 |
+
{"type": "NumType"},
|
| 49 |
+
{"type": "NumType"},
|
| 50 |
+
{"type": "NumType"},
|
| 51 |
+
{"type": "NumType"},
|
| 52 |
+
{"type": "NumType"},
|
| 53 |
+
{"type": "NumType"}]},
|
| 54 |
+
{"type": "DictType", "literals": [{"type": "LiteralStr", "value": 'Retail'}], "values": [
|
| 55 |
+
{"type": "NumType"}]},
|
| 56 |
+
{"type": "DictType", "literals": [{"type": "LiteralStr", "value": 'Delivery'}, {"type": "LiteralStr", "value": 'Resale'}, {"type": "LiteralStr", "value": 'Adjustments'}, {"type": "LiteralStr", "value": 'Transmission'}, {"type": "LiteralStr", "value": 'Other'}, {"type": "LiteralStr", "value": 'Total'}], "values": [
|
| 57 |
+
{"type": "NumType"},
|
| 58 |
+
{"type": "NumType"},
|
| 59 |
+
{"type": "NumType"},
|
| 60 |
+
{"type": "NumType"},
|
| 61 |
+
{"type": "NumType"},
|
| 62 |
+
{"type": "NumType"}]},
|
| 63 |
+
{"type": "DictType", "literals": [{"type": "LiteralStr", "value": 'Residential'}, {"type": "LiteralStr", "value": 'Commercial'}, {"type": "LiteralStr", "value": 'Industrial'}, {"type": "LiteralStr", "value": 'Transportation'}, {"type": "LiteralStr", "value": 'Total'}], "values": [
|
| 64 |
+
{"type": "DictType", "literals": [{"type": "LiteralStr", "value": 'Revenue'}, {"type": "LiteralStr", "value": 'Sales'}, {"type": "LiteralStr", "value": 'Customers'}], "values": [
|
| 65 |
+
{"type": "NumType"},
|
| 66 |
+
{"type": "NumType"},
|
| 67 |
+
{"type": "NumType"}]},
|
| 68 |
+
{"type": "DictType", "literals": [{"type": "LiteralStr", "value": 'Revenue'}, {"type": "LiteralStr", "value": 'Sales'}, {"type": "LiteralStr", "value": 'Customers'}], "values": [
|
| 69 |
+
{"type": "NumType"},
|
| 70 |
+
{"type": "NumType"},
|
| 71 |
+
{"type": "NumType"}]},
|
| 72 |
+
{"type": "DictType", "literals": [{"type": "LiteralStr", "value": 'Revenue'}, {"type": "LiteralStr", "value": 'Sales'}, {"type": "LiteralStr", "value": 'Customers'}], "values": [
|
| 73 |
+
{"type": "NumType"},
|
| 74 |
+
{"type": "NumType"},
|
| 75 |
+
{"type": "NumType"}]},
|
| 76 |
+
{"type": "DictType", "literals": [{"type": "LiteralStr", "value": 'Revenue'}, {"type": "LiteralStr", "value": 'Sales'}, {"type": "LiteralStr", "value": 'Customers'}], "values": [
|
| 77 |
+
{"type": "NumType"},
|
| 78 |
+
{"type": "NumType"},
|
| 79 |
+
{"type": "NumType"}]},
|
| 80 |
+
{"type": "DictType", "literals": [{"type": "LiteralStr", "value": 'Revenue'}, {"type": "LiteralStr", "value": 'Sales'}, {"type": "LiteralStr", "value": 'Customers'}], "values": [
|
| 81 |
+
{"type": "NumType"},
|
| 82 |
+
{"type": "NumType"},
|
| 83 |
+
{"type": "NumType"}]}]}]}}
|
| 84 |
+
},
|
| 85 |
+
}
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
class _Constants(object):
|
| 89 |
+
'''
|
| 90 |
+
Global singleton object to hide some of the constants; some IDEs reveal
|
| 91 |
+
internal module details very aggressively, and there's no other way
|
| 92 |
+
to hide stuff.
|
| 93 |
+
'''
|
| 94 |
+
|
| 95 |
+
class DatasetException(Exception):
|
| 96 |
+
''' Thrown when there is an error loading the dataset for some reason.'''
|
| 97 |
+
|
| 98 |
+
_Constants._DATABASE_NAME = _os.path.join(_os.path.dirname(__file__),
|
| 99 |
+
"electricity.data")
|
| 100 |
+
if not _os.access(_Constants._DATABASE_NAME, _os.F_OK):
|
| 101 |
+
raise DatasetException(("Error! Could not find a \"{0}\" file. "
|
| 102 |
+
"Make sure that there is a \"{0}\" in the "
|
| 103 |
+
"same directory as \"{1}.py\"! Spelling is "
|
| 104 |
+
"very important here."
|
| 105 |
+
).format(_Constants._DATABASE_NAME, __name__))
|
| 106 |
+
elif not _os.access(_Constants._DATABASE_NAME, _os.R_OK):
|
| 107 |
+
raise DatasetException(("Error! Could not read the \"{0}\" file. "
|
| 108 |
+
"Make sure that it readable by changing its "
|
| 109 |
+
"permissions. You may need to get help from "
|
| 110 |
+
"your instructor."
|
| 111 |
+
).format(_Constants._DATABASE_NAME, __name__))
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
_Constants._DATASET = None
|
| 115 |
+
|
| 116 |
+
def get_utility():
|
| 117 |
+
"""
|
| 118 |
+
Retrieves all of the utility.
|
| 119 |
+
"""
|
| 120 |
+
if _Constants._DATASET is None:
|
| 121 |
+
with open(_Constants._DATABASE_NAME, 'rb') as _:
|
| 122 |
+
_Constants._DATASET = _pickle.load(_)
|
| 123 |
+
return _Constants._DATASET
|
| 124 |
+
|
| 125 |
+
if __name__ == '__main__':
|
| 126 |
+
from pprint import pprint as _pprint
|
| 127 |
+
from timeit import default_timer as _default_timer
|
| 128 |
+
|
| 129 |
+
print(">>> get_utility()")
|
| 130 |
+
|
| 131 |
+
start_time = _default_timer()
|
| 132 |
+
result = get_utility()
|
| 133 |
+
print("Time taken: {}".format(_default_timer() - start_time))
|
| 134 |
+
_pprint(result[0])
|
images/energy_usage_ny_sankey_chart.svg
ADDED
|
|
images/energy_usage_us_sankey_chart.svg
ADDED
|
|
images/key_metrics_corr_heatmap.svg
ADDED
|
|
images/rate_disparity_dumbbell_plot.svg
ADDED
|
|
images/rate_fairness_dual_y_scatter_plot.svg
ADDED
|
|
images/top_ten_state_res_variance_table.svg
ADDED
|
|
images/utility_type_strip_plot.svg
ADDED
|
|
requirements.txt
CHANGED
|
@@ -1,3 +1,5 @@
|
|
| 1 |
-
altair
|
| 2 |
pandas
|
| 3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
pandas
|
| 2 |
+
plotly
|
| 3 |
+
scipy
|
| 4 |
+
streamlit
|
| 5 |
+
kaleido
|
src/__init__.py
ADDED
|
File without changes
|
src/streamlit_app.py
DELETED
|
@@ -1,40 +0,0 @@
|
|
| 1 |
-
import altair as alt
|
| 2 |
-
import numpy as np
|
| 3 |
-
import pandas as pd
|
| 4 |
-
import streamlit as st
|
| 5 |
-
|
| 6 |
-
"""
|
| 7 |
-
# Welcome to Streamlit!
|
| 8 |
-
|
| 9 |
-
Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
|
| 10 |
-
If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
|
| 11 |
-
forums](https://discuss.streamlit.io).
|
| 12 |
-
|
| 13 |
-
In the meantime, below is an example of what you can do with just a few lines of code:
|
| 14 |
-
"""
|
| 15 |
-
|
| 16 |
-
num_points = st.slider("Number of points in spiral", 1, 10000, 1100)
|
| 17 |
-
num_turns = st.slider("Number of turns in spiral", 1, 300, 31)
|
| 18 |
-
|
| 19 |
-
indices = np.linspace(0, 1, num_points)
|
| 20 |
-
theta = 2 * np.pi * num_turns * indices
|
| 21 |
-
radius = indices
|
| 22 |
-
|
| 23 |
-
x = radius * np.cos(theta)
|
| 24 |
-
y = radius * np.sin(theta)
|
| 25 |
-
|
| 26 |
-
df = pd.DataFrame({
|
| 27 |
-
"x": x,
|
| 28 |
-
"y": y,
|
| 29 |
-
"idx": indices,
|
| 30 |
-
"rand": np.random.randn(num_points),
|
| 31 |
-
})
|
| 32 |
-
|
| 33 |
-
st.altair_chart(alt.Chart(df, height=700, width=700)
|
| 34 |
-
.mark_point(filled=True)
|
| 35 |
-
.encode(
|
| 36 |
-
x=alt.X("x", axis=None),
|
| 37 |
-
y=alt.Y("y", axis=None),
|
| 38 |
-
color=alt.Color("idx", legend=None, scale=alt.Scale()),
|
| 39 |
-
size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])),
|
| 40 |
-
))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/util/__init__.py
ADDED
|
File without changes
|
src/util/data_util.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Functions to clean and filter data for use in utility data visualizations"""
|
| 2 |
+
|
| 3 |
+
import pandas as pd
|
| 4 |
+
|
| 5 |
+
HOURS_PER_YEAR = 8760
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def get_state_variance(df: pd.DataFrame) -> pd.DataFrame:
|
| 9 |
+
"""Summarizes key metrics for states to help choose state for analysis"""
|
| 10 |
+
|
| 11 |
+
agg_df = df.groupby('Utility.State').agg({
|
| 12 |
+
'Utility.Name': 'nunique', # Want = More utilites to analyze
|
| 13 |
+
'Utility.Type': 'nunique', # Want = More utility types to compare
|
| 14 |
+
'ResidentialUnitPrice': 'std', # Want = High std for varied utilities
|
| 15 |
+
'SystemLossPercentage': 'max', # Want = Great outlier stories
|
| 16 |
+
'IndustrialRevenueRatio': 'mean' # Want = High for industrial bias
|
| 17 |
+
})
|
| 18 |
+
|
| 19 |
+
agg_df.rename(columns={
|
| 20 |
+
'Utility.Name': '# Utilities',
|
| 21 |
+
'Utility.Type': '# Utility Types',
|
| 22 |
+
'Utility.State': 'State',
|
| 23 |
+
'ResidentialUnitPrice': 'Residential Price Std. Dev.',
|
| 24 |
+
'SystemLossPercentage': 'System Loss %',
|
| 25 |
+
'IndustrialRevenueRatio': 'Industrial Revenue %'
|
| 26 |
+
}, inplace=True)
|
| 27 |
+
|
| 28 |
+
agg_df.columns = [f"{col[0]}_{col[1]}" if isinstance(col, tuple) else col
|
| 29 |
+
for col in agg_df.columns.values]
|
| 30 |
+
agg_df.sort_values('Residential Price Std. Dev.',
|
| 31 |
+
ascending=False, inplace=True)
|
| 32 |
+
|
| 33 |
+
return agg_df.reset_index().head(10)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def get_state_data(state: str, df: pd.DataFrame) -> pd.DataFrame:
|
| 37 |
+
"""Select and filter out relevant columns for analysis"""
|
| 38 |
+
keep_columns = ["Utility.Name", "Utility.State", "Utility.Type",
|
| 39 |
+
"Sources.Total", "Sources.Generation", "Sources.Purchased",
|
| 40 |
+
"Sources.Other", "Retail.Residential.Revenue", "Retail.Residential.Sales",
|
| 41 |
+
"Retail.Residential.Customers", "Retail.Industrial.Revenue",
|
| 42 |
+
"Retail.Industrial.Sales", "Retail.Industrial.Customers",
|
| 43 |
+
"Uses.Retail", "Uses.Losses", "Uses.Resale",
|
| 44 |
+
"Uses.No Charge", "Uses.Consumed", "Uses.Total",
|
| 45 |
+
"Demand.Summer Peak", "Revenues.Retail"]
|
| 46 |
+
|
| 47 |
+
return df[df["Utility.State"] == state][keep_columns].copy()
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def prepare_data(df: pd.DataFrame) -> pd.DataFrame:
|
| 51 |
+
"""Perform calculations for key metrics and add them to the data"""
|
| 52 |
+
# Residential $ per MWh
|
| 53 |
+
df['ResidentialUnitPrice'] = (df['Retail.Residential.Revenue']
|
| 54 |
+
/ df['Retail.Residential.Sales']) * 1000
|
| 55 |
+
df['ResidentialUnitPrice'] = df['ResidentialUnitPrice'].fillna(0)
|
| 56 |
+
|
| 57 |
+
# Industrial $ per MWh
|
| 58 |
+
df['IndustrialUnitPrice'] = df['Retail.Industrial.Revenue'] / \
|
| 59 |
+
df['Retail.Industrial.Sales']
|
| 60 |
+
df['IndustrialUnitPrice'] = df['IndustrialUnitPrice'].fillna(0)
|
| 61 |
+
|
| 62 |
+
# % Dependency on industrial revenue
|
| 63 |
+
df['IndustrialRevenueRatio'] = df['Retail.Industrial.Revenue'] / \
|
| 64 |
+
df['Revenues.Retail'] * 100
|
| 65 |
+
df['IndustrialRevenueRatio'] = df['IndustrialRevenueRatio'].fillna(0)
|
| 66 |
+
|
| 67 |
+
# Equity Metric
|
| 68 |
+
df['PriceSpread'] = df['ResidentialUnitPrice'] - df['IndustrialUnitPrice']
|
| 69 |
+
|
| 70 |
+
# Efficiency Metric
|
| 71 |
+
df['SystemLossPercentage'] = (
|
| 72 |
+
df['Uses.Losses'] / df['Sources.Total']) * 100
|
| 73 |
+
|
| 74 |
+
# Operational metric of 'stress' on system
|
| 75 |
+
df['LoadFactor'] = df['Sources.Total'] / \
|
| 76 |
+
(df['Demand.Summer Peak'] * HOURS_PER_YEAR)
|
| 77 |
+
df['LoadFactor'] = df['LoadFactor'].apply(
|
| 78 |
+
lambda load: 0 if load == float('inf') else load)
|
| 79 |
+
|
| 80 |
+
return df
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def get_customer_utilities(df: pd.DataFrame, sector="Residential") -> pd.DataFrame:
|
| 84 |
+
"""Filter data by customer type for use in plots"""
|
| 85 |
+
if sector == "Residential":
|
| 86 |
+
return df[df["Retail.Residential.Customers"] > 0]
|
| 87 |
+
|
| 88 |
+
elif sector == "Industrial":
|
| 89 |
+
return df[df["Retail.Industrial.Customers"] > 0]
|
| 90 |
+
|
| 91 |
+
return df[(df["Retail.Residential.Customers"] > 0)
|
| 92 |
+
& (df["Retail.Industrial.Customers"] > 0)]
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def get_residential_load_factor(df: pd.DataFrame) -> pd.DataFrame:
|
| 96 |
+
"""Filter data by customer type and utilities with a load factor"""
|
| 97 |
+
df = get_customer_utilities(df, "Residential")
|
| 98 |
+
|
| 99 |
+
return df[df["LoadFactor"] > 0]
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def get_residential_sys_loss(df: pd.DataFrame) -> pd.DataFrame:
|
| 103 |
+
"""Filter data by customer type and utilities with system loss"""
|
| 104 |
+
df = get_customer_utilities(df, "Residential")
|
| 105 |
+
|
| 106 |
+
return df[df["SystemLossPercentage"] > 0]
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
def get_utility_usage(utility: pd.Series, level: str = "State") -> pd.DataFrame:
|
| 110 |
+
"""Create data with percentages of utilty usage within the sankey plot"""
|
| 111 |
+
|
| 112 |
+
# Convert raw values to percentages of the 'Total Sources'
|
| 113 |
+
keys = [
|
| 114 |
+
'Sources.Generation', 'Sources.Purchased', 'Sources.Other',
|
| 115 |
+
'Uses.Retail', 'Uses.Resale', 'Uses.Losses',
|
| 116 |
+
'Uses.Consumed', 'Uses.No Charge'
|
| 117 |
+
]
|
| 118 |
+
|
| 119 |
+
named_utility = (utility[keys] / utility['Sources.Total']) * 100
|
| 120 |
+
|
| 121 |
+
if level == "State":
|
| 122 |
+
named_utility['Utility.Name'] = "State of " + \
|
| 123 |
+
utility['Utility.State'][0:2]
|
| 124 |
+
elif level == "US":
|
| 125 |
+
named_utility['Utility.Name'] = "United States"
|
| 126 |
+
else:
|
| 127 |
+
named_utility['Utility.Name'] = utility['Utility.Name']
|
| 128 |
+
|
| 129 |
+
return named_utility
|
src/util/plot_util.py
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
""" Functions to create and retrieves interactive plotly charts """
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
|
| 5 |
+
import pandas as pd
|
| 6 |
+
import numpy as np
|
| 7 |
+
|
| 8 |
+
from scipy.stats import linregress
|
| 9 |
+
|
| 10 |
+
import plotly.express as px
|
| 11 |
+
import plotly.graph_objects as go
|
| 12 |
+
import plotly.io as pio
|
| 13 |
+
|
| 14 |
+
from plotly.subplots import make_subplots
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def get_state_variance_table(df: pd.DataFrame) -> go.Figure:
|
| 18 |
+
"""Retrieve table of states with highest mean residential unit price"""
|
| 19 |
+
target, row_h, header_h = 7, 35, 50
|
| 20 |
+
|
| 21 |
+
fig = go.Figure(go.Table(
|
| 22 |
+
header=dict(
|
| 23 |
+
values=list(df.columns),
|
| 24 |
+
fill_color='#f8f9fa',
|
| 25 |
+
font=dict(size=12, family="Arial Black",),
|
| 26 |
+
align='center',
|
| 27 |
+
height=header_h
|
| 28 |
+
),
|
| 29 |
+
cells=dict(
|
| 30 |
+
values=df.values.T,
|
| 31 |
+
fill_color=[['dodgerblue' if i ==
|
| 32 |
+
target else 'white' for i in range(10)]],
|
| 33 |
+
font=dict(
|
| 34 |
+
color=[['white' if i == target else 'black' for i in range(10)]], size=12),
|
| 35 |
+
height=row_h, align='center',
|
| 36 |
+
format=[None, None, None, ".2f", ".2f", ".2f"]
|
| 37 |
+
)
|
| 38 |
+
))
|
| 39 |
+
|
| 40 |
+
fig.update_layout(
|
| 41 |
+
margin=dict(l=5, r=5, t=5, b=5),
|
| 42 |
+
height=row_h * (len(df) + 2),
|
| 43 |
+
autosize=False
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
return fig
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def get_price_spread_strip_plot(df: pd.DataFrame):
|
| 50 |
+
"""
|
| 51 |
+
Strip plot of Price Spread by ownership model.
|
| 52 |
+
Shows which ownership types most consistently charge
|
| 53 |
+
residential customers more than industrial ones.
|
| 54 |
+
"""
|
| 55 |
+
fig = px.strip(
|
| 56 |
+
df[df.PriceSpread > 0],
|
| 57 |
+
x="Utility.Type",
|
| 58 |
+
y="PriceSpread",
|
| 59 |
+
color="Utility.Type",
|
| 60 |
+
hover_name="Utility.Name",
|
| 61 |
+
color_discrete_sequence=px.colors.qualitative.Prism,
|
| 62 |
+
title="<b>Rate Equity by Ownership Model:</b> "
|
| 63 |
+
"Residential Premium Over Industrial Rates",
|
| 64 |
+
labels={
|
| 65 |
+
"Utility.Type": "Type",
|
| 66 |
+
"PriceSpread": "Residential Premium ($/MWh)",
|
| 67 |
+
},
|
| 68 |
+
template="plotly_white"
|
| 69 |
+
)
|
| 70 |
+
|
| 71 |
+
fig.update_layout(showlegend=False)
|
| 72 |
+
|
| 73 |
+
return fig
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def get_key_metrics_corr_matrix(df: pd.DataFrame) -> go.Figure:
|
| 77 |
+
"""Correlation matrix for key analysis metrics"""
|
| 78 |
+
key_metrics = {
|
| 79 |
+
'SystemLossPercentage': 'System Loss %',
|
| 80 |
+
'LoadFactor': 'Load Factor',
|
| 81 |
+
'IndustrialRevenueRatio': 'Industrial Revenue %',
|
| 82 |
+
'PriceSpread': 'Price Spread',
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
corr_matrix = df[list(key_metrics.keys())].corr()
|
| 86 |
+
|
| 87 |
+
return px.imshow(
|
| 88 |
+
corr_matrix.round(2),
|
| 89 |
+
x=list(key_metrics.values()),
|
| 90 |
+
y=list(key_metrics.values()),
|
| 91 |
+
color_continuous_scale='mint',
|
| 92 |
+
text_auto=True,
|
| 93 |
+
aspect="auto",
|
| 94 |
+
title='<b>Statistical Significance:</b> Correlation Heatmap of Key Metrics',
|
| 95 |
+
labels=dict(color="Score"),
|
| 96 |
+
template='plotly_white')
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def add_fairness_trendline(fig: go.Figure, x_data: pd.Series,
|
| 100 |
+
y_data: pd.Series, row: int, col: int) -> None:
|
| 101 |
+
"""Calculates OLS and adds centered stats inside the plot to avoid title overlap."""
|
| 102 |
+
mask = ~np.isnan(x_data) & ~np.isnan(y_data)
|
| 103 |
+
x_clean, y_clean = x_data[mask], y_data[mask]
|
| 104 |
+
|
| 105 |
+
if len(x_clean) > 1:
|
| 106 |
+
# Get linear regression
|
| 107 |
+
result = linregress(x_clean, y_clean)
|
| 108 |
+
|
| 109 |
+
# Trendline coordinates
|
| 110 |
+
x_range = np.array([x_clean.min(), x_clean.max()])
|
| 111 |
+
y_range = result.slope * x_range + result.intercept
|
| 112 |
+
|
| 113 |
+
# Add Trendline
|
| 114 |
+
fig.add_trace(
|
| 115 |
+
go.Scatter(
|
| 116 |
+
x=x_range, y=y_range,
|
| 117 |
+
mode='lines',
|
| 118 |
+
line=dict(color='black', width=2, dash='dash'),
|
| 119 |
+
name='Overall Trend',
|
| 120 |
+
legendgroup='trendline',
|
| 121 |
+
showlegend=(row == 1 and col == 1),
|
| 122 |
+
hoverinfo='skip'
|
| 123 |
+
), row=row, col=col)
|
| 124 |
+
|
| 125 |
+
# 2. Annotation stats box
|
| 126 |
+
fig.add_annotation(
|
| 127 |
+
xref=f"x{col if col > 1 else ''} domain",
|
| 128 |
+
yref="y domain",
|
| 129 |
+
x=0.5, # Horizontal center
|
| 130 |
+
y=0.92, # Lowered to 92% of height (inside the plot)
|
| 131 |
+
xanchor="center",
|
| 132 |
+
yanchor="top", # Box hangs downward from the y=0.92 point
|
| 133 |
+
text=f"<b>R²:</b> {result.rvalue**2:.3f} | <b>p:</b> {result.pvalue:.4e}",
|
| 134 |
+
showarrow=False,
|
| 135 |
+
align="center",
|
| 136 |
+
# High opacity for readability
|
| 137 |
+
bgcolor="rgba(255, 255, 255, 0.85)",
|
| 138 |
+
bordercolor="rgba(0,0,0,0.3)",
|
| 139 |
+
borderwidth=1,
|
| 140 |
+
font=dict(size=10))
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
def get_fairness_dual_y_scatter_plot(df: pd.DataFrame) -> go.Figure:
|
| 144 |
+
"""Get dual y-axis scatter plot of utility fairness metrics"""
|
| 145 |
+
fig = make_subplots(
|
| 146 |
+
rows=1, cols=2,
|
| 147 |
+
shared_yaxes=True,
|
| 148 |
+
horizontal_spacing=0.05,
|
| 149 |
+
subplot_titles=('<b>System Loss vs Price</b>',
|
| 150 |
+
'<b>Load Factor vs Price</b>'))
|
| 151 |
+
|
| 152 |
+
df['BubbleSize'] = np.log1p(df['Retail.Residential.Customers'])
|
| 153 |
+
|
| 154 |
+
colors = px.colors.qualitative.Prism
|
| 155 |
+
types = df['Utility.Type'].unique()
|
| 156 |
+
color_map = {t: colors[i % len(colors)] for i, t in enumerate(types)}
|
| 157 |
+
|
| 158 |
+
# Plot 1: System Loss
|
| 159 |
+
for t in types:
|
| 160 |
+
mask = df['Utility.Type'] == t
|
| 161 |
+
fig.add_trace(
|
| 162 |
+
go.Scatter(
|
| 163 |
+
x=df[mask]['SystemLossPercentage'], y=df[mask]['ResidentialUnitPrice'],
|
| 164 |
+
name=t, hovertext=df[mask]['Utility.Name'], mode='markers',
|
| 165 |
+
marker=dict(color=color_map[t],
|
| 166 |
+
size=df[mask]['BubbleSize']),
|
| 167 |
+
hovertemplate="<b>%{hovertext}</b><br>Loss: %{x}%<br>Price: $%{y}<extra></extra>",
|
| 168 |
+
showlegend=True), row=1, col=1)
|
| 169 |
+
add_fairness_trendline(
|
| 170 |
+
fig, df['SystemLossPercentage'], df['ResidentialUnitPrice'], 1, 1)
|
| 171 |
+
|
| 172 |
+
# Plot 2: Load Factor
|
| 173 |
+
for t in types:
|
| 174 |
+
mask = df['Utility.Type'] == t
|
| 175 |
+
fig.add_trace(
|
| 176 |
+
go.Scatter(
|
| 177 |
+
x=df[mask]['LoadFactor'], y=df[mask]['ResidentialUnitPrice'], name=t,
|
| 178 |
+
mode='markers', marker=dict(color=color_map[t], size=df[mask]['BubbleSize']),
|
| 179 |
+
hovertext=df[mask]['Utility.Name'],
|
| 180 |
+
hovertemplate="<b>%{hovertext}</b><br>Load: %{x}<br>Price: $%{y}<extra></extra>",
|
| 181 |
+
showlegend=False), row=1, col=2)
|
| 182 |
+
add_fairness_trendline(fig, df['LoadFactor'],
|
| 183 |
+
df['ResidentialUnitPrice'], 1, 2)
|
| 184 |
+
|
| 185 |
+
fig.update_layout(
|
| 186 |
+
template='plotly_white',
|
| 187 |
+
title_text='<b>Fairness Audit:</b> Correlation of Utility Metrics to Residential Price',
|
| 188 |
+
legend_title_text="Ownership Model", height=600)
|
| 189 |
+
|
| 190 |
+
fig.update_yaxes(title_text='Residential Price ($/MWh)', row=1, col=1)
|
| 191 |
+
fig.update_xaxes(title_text='System Energy Loss (%)', row=1, col=1)
|
| 192 |
+
fig.update_xaxes(title_text='Load Factor', row=1, col=2)
|
| 193 |
+
|
| 194 |
+
return fig
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
def get_rate_disparity_dumbbell_plot(df: pd.DataFrame, top_n: int = 10) -> go.Figure:
|
| 198 |
+
"""Get dumbbell plot of highest disparities between industrial/residential rates"""
|
| 199 |
+
# Sort by spread to show the most "unfair" utilities at the top
|
| 200 |
+
df_sorted = df[df.PriceSpread > 0].sort_values(
|
| 201 |
+
'PriceSpread', ascending=True).tail(top_n)
|
| 202 |
+
|
| 203 |
+
fig = go.Figure()
|
| 204 |
+
|
| 205 |
+
# Add lines connecting the dots
|
| 206 |
+
for i, row in df_sorted.iterrows():
|
| 207 |
+
fig.add_shape(
|
| 208 |
+
type='line', x0=row['IndustrialUnitPrice'], x1=row['ResidentialUnitPrice'],
|
| 209 |
+
y0=row['Utility.Name'], y1=row['Utility.Name'],
|
| 210 |
+
line=dict(color='lightgrey', width=2))
|
| 211 |
+
|
| 212 |
+
# Industrial dumbbells
|
| 213 |
+
fig.add_trace(go.Scatter(
|
| 214 |
+
x=df_sorted['IndustrialUnitPrice'], y=df_sorted['Utility.Name'],
|
| 215 |
+
mode='markers', name='Industrial Rate', marker=dict(color='#1f77b4', size=10)))
|
| 216 |
+
|
| 217 |
+
# Residential dumbbells
|
| 218 |
+
fig.add_trace(go.Scatter(
|
| 219 |
+
x=df_sorted['ResidentialUnitPrice'], y=df_sorted['Utility.Name'],
|
| 220 |
+
mode='markers', name='Residential Rate', marker=dict(color='#d62728', size=10)))
|
| 221 |
+
|
| 222 |
+
fig.update_layout(title="<b>Top Rate Disparites</b>",
|
| 223 |
+
xaxis_title="Rate ($/MWh)", yaxis_title="")
|
| 224 |
+
return fig
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
def add_utility_dropdown(fig: go.Figure, df: pd.DataFrame) -> go.Figure:
|
| 228 |
+
"""Post-processing function to add a utility dropdown justified right."""
|
| 229 |
+
buttons = []
|
| 230 |
+
|
| 231 |
+
for _, r in df.iterrows():
|
| 232 |
+
buttons.append(dict(
|
| 233 |
+
method="update",
|
| 234 |
+
label=r["Utility.Name"],
|
| 235 |
+
args=[
|
| 236 |
+
{"link.value": [[
|
| 237 |
+
r["Sources.Generation"], r["Sources.Purchased"], r["Sources.Other"],
|
| 238 |
+
r["Uses.Retail"], r["Uses.Resale"], r["Uses.Losses"],
|
| 239 |
+
r["Uses.Consumed"], r["Uses.No Charge"]
|
| 240 |
+
]]},
|
| 241 |
+
{"title.text": f"<b>Energy Flow: </b>{r['Utility.Name']}"}
|
| 242 |
+
]
|
| 243 |
+
))
|
| 244 |
+
|
| 245 |
+
first_row = df.iloc[0]
|
| 246 |
+
initial_values = [
|
| 247 |
+
first_row["Sources.Generation"], first_row["Sources.Purchased"], first_row["Sources.Other"],
|
| 248 |
+
first_row["Uses.Retail"], first_row["Uses.Resale"], first_row["Uses.Losses"],
|
| 249 |
+
first_row["Uses.Consumed"], first_row["Uses.No Charge"]
|
| 250 |
+
]
|
| 251 |
+
|
| 252 |
+
# 2. Directly assign intial values to the intial Sankey
|
| 253 |
+
fig.data[0].link.value = initial_values
|
| 254 |
+
|
| 255 |
+
# 3. Apply the layout and the dropdown menu
|
| 256 |
+
fig.update_layout(
|
| 257 |
+
title_text=f"<b>Energy Flow: </b>{first_row['Utility.Name']}",
|
| 258 |
+
updatemenus=[dict(
|
| 259 |
+
buttons=buttons,
|
| 260 |
+
direction="down",
|
| 261 |
+
showactive=True,
|
| 262 |
+
x=1.0,
|
| 263 |
+
xanchor="right",
|
| 264 |
+
y=2,
|
| 265 |
+
yanchor="top",
|
| 266 |
+
active=0
|
| 267 |
+
)],
|
| 268 |
+
)
|
| 269 |
+
|
| 270 |
+
return fig
|
| 271 |
+
|
| 272 |
+
|
| 273 |
+
def get_energy_use_sankey_plot(row: pd.DataFrame) -> go.Figure:
|
| 274 |
+
"""Get energy usage sankey plot"""
|
| 275 |
+
labels = ["Generated", "Purchased", "Other", "Uses", "Retail Sales",
|
| 276 |
+
"Resale", "Losses", "Consumed", "No Charge"]
|
| 277 |
+
|
| 278 |
+
fig = go.Figure(data=[go.Sankey(
|
| 279 |
+
valueformat=".1f",
|
| 280 |
+
valuesuffix="%",
|
| 281 |
+
node=dict(
|
| 282 |
+
label=labels,
|
| 283 |
+
color=px.colors.qualitative.Prism),
|
| 284 |
+
link=dict(
|
| 285 |
+
source=[0, 1, 2, 3, 3, 3, 3, 3],
|
| 286 |
+
target=[3, 3, 3, 4, 5, 6, 7, 8],
|
| 287 |
+
value=[
|
| 288 |
+
row["Sources.Generation"],
|
| 289 |
+
row["Sources.Purchased"],
|
| 290 |
+
row["Sources.Other"],
|
| 291 |
+
row["Uses.Retail"],
|
| 292 |
+
row["Uses.Resale"],
|
| 293 |
+
row["Uses.Losses"],
|
| 294 |
+
row["Uses.Consumed"],
|
| 295 |
+
row["Uses.No Charge"]
|
| 296 |
+
],
|
| 297 |
+
))])
|
| 298 |
+
|
| 299 |
+
fig.update_layout(
|
| 300 |
+
title_text=f"<b>Energy Flow: </b>{row['Utility.Name']}",
|
| 301 |
+
hovermode='x')
|
| 302 |
+
|
| 303 |
+
return fig
|
| 304 |
+
|
| 305 |
+
|
| 306 |
+
def export_plots_as_svg(plots: list[go.Figure]) -> None:
|
| 307 |
+
"""Export plots as high-definition SVGs to the 'images' folder"""
|
| 308 |
+
|
| 309 |
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
| 310 |
+
target_dir = os.path.join(script_dir, "..", "..", "images")
|
| 311 |
+
|
| 312 |
+
if not os.path.exists(target_dir):
|
| 313 |
+
os.makedirs(target_dir)
|
| 314 |
+
|
| 315 |
+
pio.write_images(fig=plots,
|
| 316 |
+
file=["images/top_ten_state_res_variance_table.svg",
|
| 317 |
+
"images/utility_type_strip_plot.svg",
|
| 318 |
+
"images/key_metrics_corr_heatmap.svg",
|
| 319 |
+
"images/rate_fairness_dual_y_scatter_plot.svg",
|
| 320 |
+
"images/rate_disparity_dumbbell_plot.svg",
|
| 321 |
+
"images/energy_usage_ny_sankey_chart.svg",
|
| 322 |
+
"images/energy_usage_us_sankey_chart.svg"])
|
streamlit_app.py
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit.src.util.plot_util as plot_util
|
| 2 |
+
import streamlit.src.util.data_util as data_util
|
| 3 |
+
import data.electricity as electricity
|
| 4 |
+
|
| 5 |
+
import streamlit as st
|
| 6 |
+
import pandas as pd
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
st.set_page_config(
|
| 10 |
+
page_title="Utility Efficiency & Rates",
|
| 11 |
+
layout="wide"
|
| 12 |
+
)
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
@st.cache_data
|
| 16 |
+
def load_data(state: str) -> pd.DataFrame:
|
| 17 |
+
utility = electricity.get_utility()
|
| 18 |
+
df = pd.json_normalize(utility)
|
| 19 |
+
state_df = data_util.get_state_data(state, df)
|
| 20 |
+
return data_util.prepare_data(state_df), df
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
with st.sidebar:
|
| 24 |
+
state = st.selectbox(
|
| 25 |
+
"Select State",
|
| 26 |
+
options=["NY", "AK", "RI", "ME", "CA", "NJ", "CT", "NH", "MA", "AZ"],
|
| 27 |
+
index=0
|
| 28 |
+
)
|
| 29 |
+
st.markdown("---")
|
| 30 |
+
st.markdown(
|
| 31 |
+
"This app explores whether operational inefficiencies "
|
| 32 |
+
"— energy losses and poor load factors — correlate with "
|
| 33 |
+
"higher residential electricity rates."
|
| 34 |
+
)
|
| 35 |
+
st.markdown(
|
| 36 |
+
"[View on GitHub](https://github.com/chalseokorom/utilities-equity-efficiency-gap)")
|
| 37 |
+
|
| 38 |
+
state_df, full_df = load_data(state)
|
| 39 |
+
|
| 40 |
+
st.title("Electricity Utility Fairness Residential Rate Analysis")
|
| 41 |
+
st.caption(
|
| 42 |
+
f"Exploring {state} utilities — "
|
| 43 |
+
f"{len(state_df)} utilities across "
|
| 44 |
+
f"{state_df['Utility.Type'].nunique()} ownership models"
|
| 45 |
+
)
|
| 46 |
+
st.divider()
|
| 47 |
+
|
| 48 |
+
# ── Section 1: Fairness Audit ─────────────────────────────────
|
| 49 |
+
st.header("Fairness Audit — Efficiency vs. Residential Price")
|
| 50 |
+
st.caption(
|
| 51 |
+
"Do utilities with higher energy losses or lower load factors "
|
| 52 |
+
"charge residential customers more per MWh?"
|
| 53 |
+
)
|
| 54 |
+
|
| 55 |
+
scatter_df = data_util.get_residential_sys_loss(state_df)
|
| 56 |
+
scatter_df = data_util.get_residential_load_factor(scatter_df).round(2)
|
| 57 |
+
|
| 58 |
+
st.plotly_chart(
|
| 59 |
+
plot_util.get_fairness_dual_y_scatter_plot(scatter_df),
|
| 60 |
+
use_container_width=True
|
| 61 |
+
)
|
| 62 |
+
st.divider()
|
| 63 |
+
|
| 64 |
+
# ── Section 2: Ownership Models ───────────────────────────────
|
| 65 |
+
st.header("Ownership Model — Price Spread")
|
| 66 |
+
|
| 67 |
+
st.plotly_chart(
|
| 68 |
+
plot_util.get_price_spread_strip_plot(state_df.round(2)),
|
| 69 |
+
use_container_width=True
|
| 70 |
+
)
|
| 71 |
+
st.divider()
|
| 72 |
+
|
| 73 |
+
# ── Section 3: Rate Disparity ─────────────────────────────────
|
| 74 |
+
st.header("Rate Disparity — Residential vs. Industrial")
|
| 75 |
+
|
| 76 |
+
n_utilities = st.slider(
|
| 77 |
+
"Number of utilities to show",
|
| 78 |
+
min_value=5, max_value=20, value=10, step=1
|
| 79 |
+
)
|
| 80 |
+
|
| 81 |
+
both_df = data_util.get_customer_utilities(state_df, sector="Both").round(2)
|
| 82 |
+
st.plotly_chart(
|
| 83 |
+
plot_util.get_rate_disparity_dumbbell_plot(both_df, top_n=n_utilities),
|
| 84 |
+
use_container_width=True
|
| 85 |
+
)
|
| 86 |
+
st.divider()
|
| 87 |
+
|
| 88 |
+
# ── Section 4: Energy Flow ────────────────────────────────────
|
| 89 |
+
st.header("Energy Flow — Sources & Uses")
|
| 90 |
+
# Row 1: State aggregate
|
| 91 |
+
st.subheader(f"{state} — Aggregate Energy Flow")
|
| 92 |
+
st.caption(
|
| 93 |
+
"How all utilities in this state collectively source and distribute energy.")
|
| 94 |
+
|
| 95 |
+
numeric_sum = state_df.select_dtypes(include='number').sum()
|
| 96 |
+
numeric_sum['Utility.Name'] = state
|
| 97 |
+
state_flow = data_util.get_utility_usage(numeric_sum, level="US")
|
| 98 |
+
|
| 99 |
+
st.plotly_chart(
|
| 100 |
+
plot_util.get_energy_use_sankey_plot(state_flow),
|
| 101 |
+
use_container_width=True
|
| 102 |
+
)
|
| 103 |
+
|
| 104 |
+
st.divider()
|
| 105 |
+
|
| 106 |
+
# Row 2: Individual utility explorer
|
| 107 |
+
st.subheader("Individual Utility — Energy Flow")
|
| 108 |
+
st.caption(
|
| 109 |
+
"Select a utility to see its specific energy breakdown. "
|
| 110 |
+
"Compare the Losses band against the state aggregate above."
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
utility_names = sorted(state_df['Utility.Name'].dropna().unique())
|
| 114 |
+
selected_utility = st.selectbox("Select a utility", options=utility_names)
|
| 115 |
+
utility_row = state_df[state_df['Utility.Name'] == selected_utility].iloc[0]
|
| 116 |
+
utility_flow = data_util.get_utility_usage(utility_row, level="Utility")
|
| 117 |
+
|
| 118 |
+
st.plotly_chart(
|
| 119 |
+
plot_util.get_energy_use_sankey_plot(utility_flow),
|
| 120 |
+
use_container_width=True
|
| 121 |
+
)
|
utility_efficiency_fairness.ipynb
ADDED
|
@@ -0,0 +1,1622 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "markdown",
|
| 5 |
+
"id": "606940eb",
|
| 6 |
+
"metadata": {},
|
| 7 |
+
"source": [
|
| 8 |
+
"# Electricity Utility Fairness Residential Rate Analysis\n",
|
| 9 |
+
"\n",
|
| 10 |
+
"This notebook investigates a fundamental fairness question in the U.S. electricity sector: **do residential customers end up paying more when their utility operates inefficiently?**\n",
|
| 11 |
+
"\n",
|
| 12 |
+
"Using the [CORGIS Electricity Dataset](https://corgis-edu.github.io/corgis/python/electricity/) — a structured derivative of the U.S. EIA Form 861 survey — the analysis engineers a set of efficiency and equity metrics, then examines their statistical relationships through a suite of interactive visualizations. New York State serves as the primary case study."
|
| 13 |
+
]
|
| 14 |
+
},
|
| 15 |
+
{
|
| 16 |
+
"cell_type": "markdown",
|
| 17 |
+
"id": "fe53b9ab",
|
| 18 |
+
"metadata": {},
|
| 19 |
+
"source": [
|
| 20 |
+
"---\n",
|
| 21 |
+
"## 1. Setup\n",
|
| 22 |
+
"- `electricity` is the CORGIS data loader.\n",
|
| 23 |
+
"<br>\n",
|
| 24 |
+
"- `data_util` handles all data preparation and filtering. \n",
|
| 25 |
+
"- `plot_util` contains every chart-building function."
|
| 26 |
+
]
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
"cell_type": "code",
|
| 30 |
+
"execution_count": 1,
|
| 31 |
+
"id": "e862a1c5",
|
| 32 |
+
"metadata": {},
|
| 33 |
+
"outputs": [],
|
| 34 |
+
"source": [
|
| 35 |
+
"import pandas as pd\n",
|
| 36 |
+
"\n",
|
| 37 |
+
"import data.electricity as electricity\n",
|
| 38 |
+
"\n",
|
| 39 |
+
"import src.util.plot_util as plot_util\n",
|
| 40 |
+
"import src.util.data_util as data_util"
|
| 41 |
+
]
|
| 42 |
+
},
|
| 43 |
+
{
|
| 44 |
+
"cell_type": "markdown",
|
| 45 |
+
"id": "549a099a",
|
| 46 |
+
"metadata": {},
|
| 47 |
+
"source": [
|
| 48 |
+
"### 1.1 Data Loading & Metric Engineering\n",
|
| 49 |
+
"\n",
|
| 50 |
+
"The raw dataset is a list of nested dictionaries — one record per utility. `prepare_data` engineers the following derived metrics from the raw columns:\n",
|
| 51 |
+
"\n",
|
| 52 |
+
"| Metric | Formula | Interpretation |\n",
|
| 53 |
+
"|---|---|---|\n",
|
| 54 |
+
"| `Residential Unit Price` | `(Residential Revenue / Residential Sales) × 1000` | Residential rate in $/MWh |\n",
|
| 55 |
+
"| `Industrial Unit Price` | `(Industrial Revenue / Industrial Sales) × 1000` | Industrial rate in $/MWh |\n",
|
| 56 |
+
"| `Industrial Revenue Ratio` | `(Industrial Revenue / Total Retail Revenue) × 100` | % of a utility's revenue from industrial customers |\n",
|
| 57 |
+
"| `Price Spread` | `Residential Unit Price − Industrial Unit Price` | Pricing gap between Residential and Industrial customers |\n",
|
| 58 |
+
"| `System Loss Percentage` | `(Energy Losses / Total Energy) × 100` | Energy lost in transmission/distribution as % of supply |\n",
|
| 59 |
+
"| `Load Factor` | `Total Energy / (Summer Peak Demand × Hours Per Year)` | Utilization efficiency — how fully the system capacity is used |\n",
|
| 60 |
+
"\n",
|
| 61 |
+
"**Note on LoadFactor:**\n",
|
| 62 |
+
" A value of 1.0 means the utility delivers energy at exactly its peak demand capacity around the clock — theoretically perfect utilization. Real utilities typically range from 0.3–0.7. A low load factor suggests underused infrastructure whose fixed costs are spread across fewer delivered MWh."
|
| 63 |
+
]
|
| 64 |
+
},
|
| 65 |
+
{
|
| 66 |
+
"cell_type": "code",
|
| 67 |
+
"execution_count": 2,
|
| 68 |
+
"id": "6b7d5309",
|
| 69 |
+
"metadata": {},
|
| 70 |
+
"outputs": [],
|
| 71 |
+
"source": [
|
| 72 |
+
"# Retrieve entire US-based dataset\n",
|
| 73 |
+
"utility = electricity.get_utility()\n",
|
| 74 |
+
"df = pd.json_normalize(utility)\n",
|
| 75 |
+
"\n",
|
| 76 |
+
"# Limit to the state of New York and relevant columns\n",
|
| 77 |
+
"ny_df = data_util.get_state_data('NY', df)\n",
|
| 78 |
+
"\n",
|
| 79 |
+
"# Add key metrics for use in later plots\n",
|
| 80 |
+
"ny_df = data_util.prepare_data(ny_df)"
|
| 81 |
+
]
|
| 82 |
+
},
|
| 83 |
+
{
|
| 84 |
+
"cell_type": "markdown",
|
| 85 |
+
"id": "7327b62c",
|
| 86 |
+
"metadata": {},
|
| 87 |
+
"source": [
|
| 88 |
+
"---\n",
|
| 89 |
+
"## 2. State Selection — Why New York?\n",
|
| 90 |
+
"\n",
|
| 91 |
+
"Rather than choosing a state arbitrarily, I created use `get_state_variance` to rank all 50 states across five dimensions that could make for a rich case study:\n",
|
| 92 |
+
"\n",
|
| 93 |
+
"| | Why it matters |\n",
|
| 94 |
+
"|---|---|\n",
|
| 95 |
+
"| **# Utilities** | More utilities = more data points and statistical power |\n",
|
| 96 |
+
"| **# Utility Types** | More ownership models = richer segmentation and comparison |\n",
|
| 97 |
+
"| **Residential Price Std. Dev.** | High spread = real price variation to explain, not a flat market |\n",
|
| 98 |
+
"| **Max System Loss %** | Outlier utilities = compelling examples for the core hypothesis |\n",
|
| 99 |
+
"| **Industrial Revenue %** | High industrial dependency = cross-subsidization dynamics worth examining |\n",
|
| 100 |
+
"\n",
|
| 101 |
+
"The table below highlights the top 10 states by residential price variance. **New York (highlighted)** ranks highly because it uniquely satisfies *all five* criteria simultaneously."
|
| 102 |
+
]
|
| 103 |
+
},
|
| 104 |
+
{
|
| 105 |
+
"cell_type": "code",
|
| 106 |
+
"execution_count": 3,
|
| 107 |
+
"id": "ad5df068",
|
| 108 |
+
"metadata": {},
|
| 109 |
+
"outputs": [],
|
| 110 |
+
"source": [
|
| 111 |
+
"state_variance = data_util.get_state_variance(data_util.prepare_data(df))\n",
|
| 112 |
+
"\n",
|
| 113 |
+
"top_variance_table = plot_util.get_state_variance_table(state_variance)\n",
|
| 114 |
+
"\n",
|
| 115 |
+
"# top_variance_table.show()"
|
| 116 |
+
]
|
| 117 |
+
},
|
| 118 |
+
{
|
| 119 |
+
"cell_type": "markdown",
|
| 120 |
+
"id": "3455bc7c",
|
| 121 |
+
"metadata": {},
|
| 122 |
+
"source": [
|
| 123 |
+
"### Why New York specifically?\n",
|
| 124 |
+
"\n",
|
| 125 |
+
"Beyond the rankings, New York offers several structural advantages for this analysis:\n",
|
| 126 |
+
"\n",
|
| 127 |
+
"- **6 distinct utility ownership types** — more than almost any other state, enabling meaningful cross-model comparisons within a single regulatory environment.\n",
|
| 128 |
+
"- **100+ individual utilities** — sufficient sample size to draw statistically meaningful conclusions.\n",
|
| 129 |
+
"- **Extreme geographic diversity** — Con Edison serves dense urban Manhattan while small rural cooperatives serve upstate communities. This natural variation creates a richer distribution of efficiency and pricing outcomes than a more homogeneous state would.\n",
|
| 130 |
+
"- **High system loss outliers** — NY's transmission infrastructure includes some of the oldest urban grid segments in the country, producing the high-loss edge cases that make the core hypothesis testable.\n",
|
| 131 |
+
"- **Active regulatory environment** — the NY Public Service Commission is one of the most scrutinized utility regulators in the U.S., meaning this analysis connects directly to real-world policy questions."
|
| 132 |
+
]
|
| 133 |
+
},
|
| 134 |
+
{
|
| 135 |
+
"cell_type": "markdown",
|
| 136 |
+
"id": "1992b6ef",
|
| 137 |
+
"metadata": {},
|
| 138 |
+
"source": [
|
| 139 |
+
"---\n",
|
| 140 |
+
"## 3. Ownership Model Analysis\n",
|
| 141 |
+
"\n",
|
| 142 |
+
"Before testing the efficiency hypothesis, we establish baseline pricing distributions by ownership model. This matters because ownership type is a structural variable — investor-owned utilities (IOUs) answer to shareholders and profit motives, while cooperatives and municipals serve member-owners or public ratepayers. If ownership type already explains most of the price variance, efficiency metrics may be redundant."
|
| 143 |
+
]
|
| 144 |
+
},
|
| 145 |
+
{
|
| 146 |
+
"cell_type": "code",
|
| 147 |
+
"execution_count": 11,
|
| 148 |
+
"id": "54f2b308",
|
| 149 |
+
"metadata": {},
|
| 150 |
+
"outputs": [
|
| 151 |
+
{
|
| 152 |
+
"data": {
|
| 153 |
+
"application/vnd.plotly.v1+json": {
|
| 154 |
+
"config": {
|
| 155 |
+
"plotlyServerURL": "https://plot.ly"
|
| 156 |
+
},
|
| 157 |
+
"data": [
|
| 158 |
+
{
|
| 159 |
+
"alignmentgroup": "True",
|
| 160 |
+
"boxpoints": "all",
|
| 161 |
+
"fillcolor": "rgba(255,255,255,0)",
|
| 162 |
+
"hoveron": "points",
|
| 163 |
+
"hovertemplate": "<b>%{hovertext}</b><br><br>Type=%{x}<br>Residential Premium ($/MWh)=%{y}<extra></extra>",
|
| 164 |
+
"hovertext": [
|
| 165 |
+
"Agway Energy Services, LLC",
|
| 166 |
+
"Energy Coop of New York, Inc",
|
| 167 |
+
"Robison Energy, LLC",
|
| 168 |
+
"U.S. Energy Partners LLC",
|
| 169 |
+
"NOCO Electric",
|
| 170 |
+
"Columbia Utilities Power, LLC",
|
| 171 |
+
"Hudson Energy Services",
|
| 172 |
+
"Palmco Power NJ, LLC",
|
| 173 |
+
"Major Energy Electric Services",
|
| 174 |
+
"Palmco Power PA, LLC",
|
| 175 |
+
"Palmco Power MD, LLC",
|
| 176 |
+
"Plymouth Rock Energy, LLC",
|
| 177 |
+
"Palmco Power OH, LLC",
|
| 178 |
+
"Stream Energy New York, LLC",
|
| 179 |
+
"EnergyMark, LLC",
|
| 180 |
+
"Palmco Power IL, LLC",
|
| 181 |
+
"Respond Power LLC",
|
| 182 |
+
"Kiwi Energy NY LLC",
|
| 183 |
+
"SmartEnergy Holdings, LLC",
|
| 184 |
+
"Alpha Gas and Electric, LLC",
|
| 185 |
+
"Energy.Me Midwest LLC",
|
| 186 |
+
"Marathon Power LLC",
|
| 187 |
+
"Harborside Energy, LLC",
|
| 188 |
+
"Abest Power & Gas, LLC",
|
| 189 |
+
"MPower Energy LLC",
|
| 190 |
+
"Palmco Power MA, LLC",
|
| 191 |
+
"Agera Energy LLC",
|
| 192 |
+
"Greenlight Energy Inc.",
|
| 193 |
+
"New Wave Energy Corporation",
|
| 194 |
+
"HIKO Energy, LLC",
|
| 195 |
+
"Aequitas Energy Inc",
|
| 196 |
+
"Great Eastern Energy",
|
| 197 |
+
"East Coast Power & Gas, LLC",
|
| 198 |
+
"Spring Energy RRH LLC dba Spring Power &",
|
| 199 |
+
"MPower Energy NJ LLC",
|
| 200 |
+
"Atlantic Energy LLC",
|
| 201 |
+
"Palmco Power DC, LLC",
|
| 202 |
+
"Palmco Power DE, LLC",
|
| 203 |
+
"South Bay Energy Corp.",
|
| 204 |
+
"Flanders Energy, LLC",
|
| 205 |
+
"Pure Energy USA, LLC",
|
| 206 |
+
"First Choice Energy LLC",
|
| 207 |
+
"Agressive Energy LLC"
|
| 208 |
+
],
|
| 209 |
+
"legendgroup": "Retail Power Marketer",
|
| 210 |
+
"line": {
|
| 211 |
+
"color": "rgba(255,255,255,0)"
|
| 212 |
+
},
|
| 213 |
+
"marker": {
|
| 214 |
+
"color": "rgb(95, 70, 144)"
|
| 215 |
+
},
|
| 216 |
+
"name": "Retail Power Marketer",
|
| 217 |
+
"offsetgroup": "Retail Power Marketer",
|
| 218 |
+
"orientation": "v",
|
| 219 |
+
"pointpos": 0,
|
| 220 |
+
"showlegend": true,
|
| 221 |
+
"type": "box",
|
| 222 |
+
"x": [
|
| 223 |
+
"Retail Power Marketer",
|
| 224 |
+
"Retail Power Marketer",
|
| 225 |
+
"Retail Power Marketer",
|
| 226 |
+
"Retail Power Marketer",
|
| 227 |
+
"Retail Power Marketer",
|
| 228 |
+
"Retail Power Marketer",
|
| 229 |
+
"Retail Power Marketer",
|
| 230 |
+
"Retail Power Marketer",
|
| 231 |
+
"Retail Power Marketer",
|
| 232 |
+
"Retail Power Marketer",
|
| 233 |
+
"Retail Power Marketer",
|
| 234 |
+
"Retail Power Marketer",
|
| 235 |
+
"Retail Power Marketer",
|
| 236 |
+
"Retail Power Marketer",
|
| 237 |
+
"Retail Power Marketer",
|
| 238 |
+
"Retail Power Marketer",
|
| 239 |
+
"Retail Power Marketer",
|
| 240 |
+
"Retail Power Marketer",
|
| 241 |
+
"Retail Power Marketer",
|
| 242 |
+
"Retail Power Marketer",
|
| 243 |
+
"Retail Power Marketer",
|
| 244 |
+
"Retail Power Marketer",
|
| 245 |
+
"Retail Power Marketer",
|
| 246 |
+
"Retail Power Marketer",
|
| 247 |
+
"Retail Power Marketer",
|
| 248 |
+
"Retail Power Marketer",
|
| 249 |
+
"Retail Power Marketer",
|
| 250 |
+
"Retail Power Marketer",
|
| 251 |
+
"Retail Power Marketer",
|
| 252 |
+
"Retail Power Marketer",
|
| 253 |
+
"Retail Power Marketer",
|
| 254 |
+
"Retail Power Marketer",
|
| 255 |
+
"Retail Power Marketer",
|
| 256 |
+
"Retail Power Marketer",
|
| 257 |
+
"Retail Power Marketer",
|
| 258 |
+
"Retail Power Marketer",
|
| 259 |
+
"Retail Power Marketer",
|
| 260 |
+
"Retail Power Marketer",
|
| 261 |
+
"Retail Power Marketer",
|
| 262 |
+
"Retail Power Marketer",
|
| 263 |
+
"Retail Power Marketer",
|
| 264 |
+
"Retail Power Marketer",
|
| 265 |
+
"Retail Power Marketer"
|
| 266 |
+
],
|
| 267 |
+
"x0": " ",
|
| 268 |
+
"xaxis": "x",
|
| 269 |
+
"y": {
|
| 270 |
+
"bdata": "1HdJqZaIVEDlcPM237NFQJ4t+TeSfVVA9Y6hQZjxREC7n0AeSF5FQAJayxan5mdA+NB2EXoPWEDkHoKBHplrQAf1wZiCkFxAdfEDDupzYUBcKbC7K7hjQNg9bETnNGBAwv1PMJqEXEBzGFLAfrxQQMb9Fc3b/0VA4ydnH2Q/VkBOiMxbaUtiQE1aVCBa8FtAdyZPlzdMWUD/BbayJZdaQNFZ0CR2H1NAVaW6iXyDUkAWCcnsvT5kQL6sGTMR6VRAOFE8FqijYEDwxWACoKxkQLReHZWsKFhANJ/x5rw1XUAUtkGpzFhMQEG4G/seUWFA7ZpZKnJNXEC+qlbjMDJZQMjcovLsXVRAJYv3tlpiWkAv2u+X74BeQM7YHwYLZ2BAFkOHeY7iW0DXGD4bRKpZQNjxRK4pNV9AnhCBas4qZEATgY2Ufz1jQNvxsf19yVhAbl5aGi50UUA=",
|
| 271 |
+
"dtype": "f8"
|
| 272 |
+
},
|
| 273 |
+
"y0": " ",
|
| 274 |
+
"yaxis": "y"
|
| 275 |
+
},
|
| 276 |
+
{
|
| 277 |
+
"alignmentgroup": "True",
|
| 278 |
+
"boxpoints": "all",
|
| 279 |
+
"fillcolor": "rgba(255,255,255,0)",
|
| 280 |
+
"hoveron": "points",
|
| 281 |
+
"hovertemplate": "<b>%{hovertext}</b><br><br>Type=%{x}<br>Residential Premium ($/MWh)=%{y}<extra></extra>",
|
| 282 |
+
"hovertext": [
|
| 283 |
+
"Village of Arcade - (NY)",
|
| 284 |
+
"Village of Akron - (NY)",
|
| 285 |
+
"Village of Andover - (NY)",
|
| 286 |
+
"Village of Angelica - (NY)",
|
| 287 |
+
"Bath Electric Gas & Water",
|
| 288 |
+
"Village of Bergen - (NY)",
|
| 289 |
+
"Village of Boonville - (NY)",
|
| 290 |
+
"Village of Brocton - (NY)",
|
| 291 |
+
"Village of Castile - (NY)",
|
| 292 |
+
"Village of Churchville - (NY)",
|
| 293 |
+
"Village of Endicott - (NY)",
|
| 294 |
+
"Village of Fairport - (NY)",
|
| 295 |
+
"Village of Frankfort - (NY)",
|
| 296 |
+
"Village of Freeport - (NY)",
|
| 297 |
+
"Village of Green Island - (NY)",
|
| 298 |
+
"Village of Greene - (NY)",
|
| 299 |
+
"Village of Groton - (NY)",
|
| 300 |
+
"Village of Hamilton - (NY)",
|
| 301 |
+
"Village of Holley - (NY)",
|
| 302 |
+
"Village of Ilion - (NY)",
|
| 303 |
+
"Jamestown Board of Public Util",
|
| 304 |
+
"Lake Placid Village, Inc - (NY)",
|
| 305 |
+
"Village of Little Valley - (NY)",
|
| 306 |
+
"Village of Marathon - (NY)",
|
| 307 |
+
"Town of Massena - (NY)",
|
| 308 |
+
"Village of Mayville - (NY)",
|
| 309 |
+
"Mohawk Municipal Comm",
|
| 310 |
+
"Village of Penn Yan - (NY)",
|
| 311 |
+
"Village of Philadelphia - (NY)",
|
| 312 |
+
"City of Plattsburgh - (NY)",
|
| 313 |
+
"Village of Richmondville - (NY)",
|
| 314 |
+
"Village of Rockville Centre - (NY)",
|
| 315 |
+
"Village of Rouses Point - (NY)",
|
| 316 |
+
"City of Salamanca - (NY)",
|
| 317 |
+
"Village of Sherburne - (NY)",
|
| 318 |
+
"City of Sherrill - (NY)",
|
| 319 |
+
"Village of Silver Springs - (NY)",
|
| 320 |
+
"Village of Skaneateles - (NY)",
|
| 321 |
+
"Village of Solvay - (NY)",
|
| 322 |
+
"Village of Spencerport - (NY)",
|
| 323 |
+
"Village of Springville - (NY)",
|
| 324 |
+
"Village of Theresa - (NY)",
|
| 325 |
+
"Village of Tupper Lake - (NY)",
|
| 326 |
+
"Village of Watkins Glen - (NY)",
|
| 327 |
+
"Village of Wellsville - (NY)",
|
| 328 |
+
"Village of Westfield - (NY)"
|
| 329 |
+
],
|
| 330 |
+
"legendgroup": "Municipal",
|
| 331 |
+
"line": {
|
| 332 |
+
"color": "rgba(255,255,255,0)"
|
| 333 |
+
},
|
| 334 |
+
"marker": {
|
| 335 |
+
"color": "rgb(29, 105, 150)"
|
| 336 |
+
},
|
| 337 |
+
"name": "Municipal",
|
| 338 |
+
"offsetgroup": "Municipal",
|
| 339 |
+
"orientation": "v",
|
| 340 |
+
"pointpos": 0,
|
| 341 |
+
"showlegend": true,
|
| 342 |
+
"type": "box",
|
| 343 |
+
"x": [
|
| 344 |
+
"Municipal",
|
| 345 |
+
"Municipal",
|
| 346 |
+
"Municipal",
|
| 347 |
+
"Municipal",
|
| 348 |
+
"Municipal",
|
| 349 |
+
"Municipal",
|
| 350 |
+
"Municipal",
|
| 351 |
+
"Municipal",
|
| 352 |
+
"Municipal",
|
| 353 |
+
"Municipal",
|
| 354 |
+
"Municipal",
|
| 355 |
+
"Municipal",
|
| 356 |
+
"Municipal",
|
| 357 |
+
"Municipal",
|
| 358 |
+
"Municipal",
|
| 359 |
+
"Municipal",
|
| 360 |
+
"Municipal",
|
| 361 |
+
"Municipal",
|
| 362 |
+
"Municipal",
|
| 363 |
+
"Municipal",
|
| 364 |
+
"Municipal",
|
| 365 |
+
"Municipal",
|
| 366 |
+
"Municipal",
|
| 367 |
+
"Municipal",
|
| 368 |
+
"Municipal",
|
| 369 |
+
"Municipal",
|
| 370 |
+
"Municipal",
|
| 371 |
+
"Municipal",
|
| 372 |
+
"Municipal",
|
| 373 |
+
"Municipal",
|
| 374 |
+
"Municipal",
|
| 375 |
+
"Municipal",
|
| 376 |
+
"Municipal",
|
| 377 |
+
"Municipal",
|
| 378 |
+
"Municipal",
|
| 379 |
+
"Municipal",
|
| 380 |
+
"Municipal",
|
| 381 |
+
"Municipal",
|
| 382 |
+
"Municipal",
|
| 383 |
+
"Municipal",
|
| 384 |
+
"Municipal",
|
| 385 |
+
"Municipal",
|
| 386 |
+
"Municipal",
|
| 387 |
+
"Municipal",
|
| 388 |
+
"Municipal",
|
| 389 |
+
"Municipal"
|
| 390 |
+
],
|
| 391 |
+
"x0": " ",
|
| 392 |
+
"xaxis": "x",
|
| 393 |
+
"y": {
|
| 394 |
+
"bdata": "4zx0l/glSkCrn6PKMxZKQDFIRJo2x0VAGOWbWs1/T0CCUB+yIaJNQEZpzEOSWVNAykfneWTjSUC5RSoUu+tGQD5k23fSFk1Azmj3sPXNUkDJCaumGFlQQBb2vzXcvktA7+AqSao3Q0CQqdv9nMlfQC8zhjbsPltAXfLkhENnS0DUHfrfiDZKQJdz0sw7pkhAl5Nt4wsgTkCCI8bcBMRKQNIB9qAznlRAw5X6vW53S0At0Q+nexBTQCjVNgwQnVFASdVlz85LTkAsdSyDtF5QQNhzo/V/JkVArLuSbKG6QkCXOXo4ck9OQMfHf05/EUdAK8FbUIDWUkDSX/M3ctteQF6gAPx8Ej1Aw45Qct0fSUC0jal315FLQNRIT1cjHU1AjVKkDWvMUUDTH1W7sehEQAeqFkKflkxAe68yr+vZRkBpZcd2fvFKQMx1qVre1FNApaDbJmbZSkCWccuMD2tCQNSJR5YBWU5ARDUJMx5yTEA=",
|
| 395 |
+
"dtype": "f8"
|
| 396 |
+
},
|
| 397 |
+
"y0": " ",
|
| 398 |
+
"yaxis": "y"
|
| 399 |
+
},
|
| 400 |
+
{
|
| 401 |
+
"alignmentgroup": "True",
|
| 402 |
+
"boxpoints": "all",
|
| 403 |
+
"fillcolor": "rgba(255,255,255,0)",
|
| 404 |
+
"hoveron": "points",
|
| 405 |
+
"hovertemplate": "<b>%{hovertext}</b><br><br>Type=%{x}<br>Residential Premium ($/MWh)=%{y}<extra></extra>",
|
| 406 |
+
"hovertext": [
|
| 407 |
+
"Central Hudson Gas & Elec Corp",
|
| 408 |
+
"Consolidated Edison Co-NY Inc",
|
| 409 |
+
"Fishers Island Utility Co Inc",
|
| 410 |
+
"Massachusetts Electric Co",
|
| 411 |
+
"New York State Elec & Gas Corp",
|
| 412 |
+
"Niagara Mohawk Power Corp.",
|
| 413 |
+
"Orange & Rockland Utils Inc",
|
| 414 |
+
"Pike County Light & Power Co",
|
| 415 |
+
"Rochester Gas & Electric Corp",
|
| 416 |
+
"Rockland Electric Co"
|
| 417 |
+
],
|
| 418 |
+
"legendgroup": "Investor Owned",
|
| 419 |
+
"line": {
|
| 420 |
+
"color": "rgba(255,255,255,0)"
|
| 421 |
+
},
|
| 422 |
+
"marker": {
|
| 423 |
+
"color": "rgb(56, 166, 165)"
|
| 424 |
+
},
|
| 425 |
+
"name": "Investor Owned",
|
| 426 |
+
"offsetgroup": "Investor Owned",
|
| 427 |
+
"orientation": "v",
|
| 428 |
+
"pointpos": 0,
|
| 429 |
+
"showlegend": true,
|
| 430 |
+
"type": "box",
|
| 431 |
+
"x": [
|
| 432 |
+
"Investor Owned",
|
| 433 |
+
"Investor Owned",
|
| 434 |
+
"Investor Owned",
|
| 435 |
+
"Investor Owned",
|
| 436 |
+
"Investor Owned",
|
| 437 |
+
"Investor Owned",
|
| 438 |
+
"Investor Owned",
|
| 439 |
+
"Investor Owned",
|
| 440 |
+
"Investor Owned",
|
| 441 |
+
"Investor Owned"
|
| 442 |
+
],
|
| 443 |
+
"x0": " ",
|
| 444 |
+
"xaxis": "x",
|
| 445 |
+
"y": {
|
| 446 |
+
"bdata": "DlfHeglgZEAky8QEPyJtQOHh4eHh4XNAiNVwftXYZkBAg+xCRm5aQLlstvUwUV1AP0jm2o/iZEC7M68wZX1eQCRaHECiYF5AzRC7X0JqY0A=",
|
| 447 |
+
"dtype": "f8"
|
| 448 |
+
},
|
| 449 |
+
"y0": " ",
|
| 450 |
+
"yaxis": "y"
|
| 451 |
+
},
|
| 452 |
+
{
|
| 453 |
+
"alignmentgroup": "True",
|
| 454 |
+
"boxpoints": "all",
|
| 455 |
+
"fillcolor": "rgba(255,255,255,0)",
|
| 456 |
+
"hoveron": "points",
|
| 457 |
+
"hovertemplate": "<b>%{hovertext}</b><br><br>Type=%{x}<br>Residential Premium ($/MWh)=%{y}<extra></extra>",
|
| 458 |
+
"hovertext": [
|
| 459 |
+
"Delaware County Elec Coop Inc",
|
| 460 |
+
"North Shore Towers Apts Inc",
|
| 461 |
+
"Otsego Electric Coop, Inc",
|
| 462 |
+
"Steuben Rural Elec Coop, Inc"
|
| 463 |
+
],
|
| 464 |
+
"legendgroup": "Cooperative",
|
| 465 |
+
"line": {
|
| 466 |
+
"color": "rgba(255,255,255,0)"
|
| 467 |
+
},
|
| 468 |
+
"marker": {
|
| 469 |
+
"color": "rgb(15, 133, 84)"
|
| 470 |
+
},
|
| 471 |
+
"name": "Cooperative",
|
| 472 |
+
"offsetgroup": "Cooperative",
|
| 473 |
+
"orientation": "v",
|
| 474 |
+
"pointpos": 0,
|
| 475 |
+
"showlegend": true,
|
| 476 |
+
"type": "box",
|
| 477 |
+
"x": [
|
| 478 |
+
"Cooperative",
|
| 479 |
+
"Cooperative",
|
| 480 |
+
"Cooperative",
|
| 481 |
+
"Cooperative"
|
| 482 |
+
],
|
| 483 |
+
"x0": " ",
|
| 484 |
+
"xaxis": "x",
|
| 485 |
+
"y": {
|
| 486 |
+
"bdata": "+aWVpF9ZYUDMuXx9SDRxQE3e4teNEl9Aw0BzNP3fXkA=",
|
| 487 |
+
"dtype": "f8"
|
| 488 |
+
},
|
| 489 |
+
"y0": " ",
|
| 490 |
+
"yaxis": "y"
|
| 491 |
+
},
|
| 492 |
+
{
|
| 493 |
+
"alignmentgroup": "True",
|
| 494 |
+
"boxpoints": "all",
|
| 495 |
+
"fillcolor": "rgba(255,255,255,0)",
|
| 496 |
+
"hoveron": "points",
|
| 497 |
+
"hovertemplate": "<b>%{hovertext}</b><br><br>Type=%{x}<br>Residential Premium ($/MWh)=%{y}<extra></extra>",
|
| 498 |
+
"hovertext": [
|
| 499 |
+
"Long Island Power Authority"
|
| 500 |
+
],
|
| 501 |
+
"legendgroup": "State",
|
| 502 |
+
"line": {
|
| 503 |
+
"color": "rgba(255,255,255,0)"
|
| 504 |
+
},
|
| 505 |
+
"marker": {
|
| 506 |
+
"color": "rgb(115, 175, 72)"
|
| 507 |
+
},
|
| 508 |
+
"name": "State",
|
| 509 |
+
"offsetgroup": "State",
|
| 510 |
+
"orientation": "v",
|
| 511 |
+
"pointpos": 0,
|
| 512 |
+
"showlegend": true,
|
| 513 |
+
"type": "box",
|
| 514 |
+
"x": [
|
| 515 |
+
"State"
|
| 516 |
+
],
|
| 517 |
+
"x0": " ",
|
| 518 |
+
"xaxis": "x",
|
| 519 |
+
"y": {
|
| 520 |
+
"bdata": "WOYOiWF7aUA=",
|
| 521 |
+
"dtype": "f8"
|
| 522 |
+
},
|
| 523 |
+
"y0": " ",
|
| 524 |
+
"yaxis": "y"
|
| 525 |
+
},
|
| 526 |
+
{
|
| 527 |
+
"alignmentgroup": "True",
|
| 528 |
+
"boxpoints": "all",
|
| 529 |
+
"fillcolor": "rgba(255,255,255,0)",
|
| 530 |
+
"hoveron": "points",
|
| 531 |
+
"hovertemplate": "<b>%{hovertext}</b><br><br>Type=%{x}<br>Residential Premium ($/MWh)=%{y}<extra></extra>",
|
| 532 |
+
"hovertext": [
|
| 533 |
+
"Greenbacker Renewable Energy Corporation"
|
| 534 |
+
],
|
| 535 |
+
"legendgroup": "Behind the Meter",
|
| 536 |
+
"line": {
|
| 537 |
+
"color": "rgba(255,255,255,0)"
|
| 538 |
+
},
|
| 539 |
+
"marker": {
|
| 540 |
+
"color": "rgb(237, 173, 8)"
|
| 541 |
+
},
|
| 542 |
+
"name": "Behind the Meter",
|
| 543 |
+
"offsetgroup": "Behind the Meter",
|
| 544 |
+
"orientation": "v",
|
| 545 |
+
"pointpos": 0,
|
| 546 |
+
"showlegend": true,
|
| 547 |
+
"type": "box",
|
| 548 |
+
"x": [
|
| 549 |
+
"Behind the Meter"
|
| 550 |
+
],
|
| 551 |
+
"x0": " ",
|
| 552 |
+
"xaxis": "x",
|
| 553 |
+
"y": {
|
| 554 |
+
"bdata": "tp977y/kYkA=",
|
| 555 |
+
"dtype": "f8"
|
| 556 |
+
},
|
| 557 |
+
"y0": " ",
|
| 558 |
+
"yaxis": "y"
|
| 559 |
+
}
|
| 560 |
+
],
|
| 561 |
+
"layout": {
|
| 562 |
+
"boxmode": "overlay",
|
| 563 |
+
"legend": {
|
| 564 |
+
"title": {
|
| 565 |
+
"text": "Type"
|
| 566 |
+
},
|
| 567 |
+
"tracegroupgap": 0
|
| 568 |
+
},
|
| 569 |
+
"showlegend": false,
|
| 570 |
+
"template": {
|
| 571 |
+
"data": {
|
| 572 |
+
"bar": [
|
| 573 |
+
{
|
| 574 |
+
"error_x": {
|
| 575 |
+
"color": "#2a3f5f"
|
| 576 |
+
},
|
| 577 |
+
"error_y": {
|
| 578 |
+
"color": "#2a3f5f"
|
| 579 |
+
},
|
| 580 |
+
"marker": {
|
| 581 |
+
"line": {
|
| 582 |
+
"color": "white",
|
| 583 |
+
"width": 0.5
|
| 584 |
+
},
|
| 585 |
+
"pattern": {
|
| 586 |
+
"fillmode": "overlay",
|
| 587 |
+
"size": 10,
|
| 588 |
+
"solidity": 0.2
|
| 589 |
+
}
|
| 590 |
+
},
|
| 591 |
+
"type": "bar"
|
| 592 |
+
}
|
| 593 |
+
],
|
| 594 |
+
"barpolar": [
|
| 595 |
+
{
|
| 596 |
+
"marker": {
|
| 597 |
+
"line": {
|
| 598 |
+
"color": "white",
|
| 599 |
+
"width": 0.5
|
| 600 |
+
},
|
| 601 |
+
"pattern": {
|
| 602 |
+
"fillmode": "overlay",
|
| 603 |
+
"size": 10,
|
| 604 |
+
"solidity": 0.2
|
| 605 |
+
}
|
| 606 |
+
},
|
| 607 |
+
"type": "barpolar"
|
| 608 |
+
}
|
| 609 |
+
],
|
| 610 |
+
"carpet": [
|
| 611 |
+
{
|
| 612 |
+
"aaxis": {
|
| 613 |
+
"endlinecolor": "#2a3f5f",
|
| 614 |
+
"gridcolor": "#C8D4E3",
|
| 615 |
+
"linecolor": "#C8D4E3",
|
| 616 |
+
"minorgridcolor": "#C8D4E3",
|
| 617 |
+
"startlinecolor": "#2a3f5f"
|
| 618 |
+
},
|
| 619 |
+
"baxis": {
|
| 620 |
+
"endlinecolor": "#2a3f5f",
|
| 621 |
+
"gridcolor": "#C8D4E3",
|
| 622 |
+
"linecolor": "#C8D4E3",
|
| 623 |
+
"minorgridcolor": "#C8D4E3",
|
| 624 |
+
"startlinecolor": "#2a3f5f"
|
| 625 |
+
},
|
| 626 |
+
"type": "carpet"
|
| 627 |
+
}
|
| 628 |
+
],
|
| 629 |
+
"choropleth": [
|
| 630 |
+
{
|
| 631 |
+
"colorbar": {
|
| 632 |
+
"outlinewidth": 0,
|
| 633 |
+
"ticks": ""
|
| 634 |
+
},
|
| 635 |
+
"type": "choropleth"
|
| 636 |
+
}
|
| 637 |
+
],
|
| 638 |
+
"contour": [
|
| 639 |
+
{
|
| 640 |
+
"colorbar": {
|
| 641 |
+
"outlinewidth": 0,
|
| 642 |
+
"ticks": ""
|
| 643 |
+
},
|
| 644 |
+
"colorscale": [
|
| 645 |
+
[
|
| 646 |
+
0,
|
| 647 |
+
"#0d0887"
|
| 648 |
+
],
|
| 649 |
+
[
|
| 650 |
+
0.1111111111111111,
|
| 651 |
+
"#46039f"
|
| 652 |
+
],
|
| 653 |
+
[
|
| 654 |
+
0.2222222222222222,
|
| 655 |
+
"#7201a8"
|
| 656 |
+
],
|
| 657 |
+
[
|
| 658 |
+
0.3333333333333333,
|
| 659 |
+
"#9c179e"
|
| 660 |
+
],
|
| 661 |
+
[
|
| 662 |
+
0.4444444444444444,
|
| 663 |
+
"#bd3786"
|
| 664 |
+
],
|
| 665 |
+
[
|
| 666 |
+
0.5555555555555556,
|
| 667 |
+
"#d8576b"
|
| 668 |
+
],
|
| 669 |
+
[
|
| 670 |
+
0.6666666666666666,
|
| 671 |
+
"#ed7953"
|
| 672 |
+
],
|
| 673 |
+
[
|
| 674 |
+
0.7777777777777778,
|
| 675 |
+
"#fb9f3a"
|
| 676 |
+
],
|
| 677 |
+
[
|
| 678 |
+
0.8888888888888888,
|
| 679 |
+
"#fdca26"
|
| 680 |
+
],
|
| 681 |
+
[
|
| 682 |
+
1,
|
| 683 |
+
"#f0f921"
|
| 684 |
+
]
|
| 685 |
+
],
|
| 686 |
+
"type": "contour"
|
| 687 |
+
}
|
| 688 |
+
],
|
| 689 |
+
"contourcarpet": [
|
| 690 |
+
{
|
| 691 |
+
"colorbar": {
|
| 692 |
+
"outlinewidth": 0,
|
| 693 |
+
"ticks": ""
|
| 694 |
+
},
|
| 695 |
+
"type": "contourcarpet"
|
| 696 |
+
}
|
| 697 |
+
],
|
| 698 |
+
"heatmap": [
|
| 699 |
+
{
|
| 700 |
+
"colorbar": {
|
| 701 |
+
"outlinewidth": 0,
|
| 702 |
+
"ticks": ""
|
| 703 |
+
},
|
| 704 |
+
"colorscale": [
|
| 705 |
+
[
|
| 706 |
+
0,
|
| 707 |
+
"#0d0887"
|
| 708 |
+
],
|
| 709 |
+
[
|
| 710 |
+
0.1111111111111111,
|
| 711 |
+
"#46039f"
|
| 712 |
+
],
|
| 713 |
+
[
|
| 714 |
+
0.2222222222222222,
|
| 715 |
+
"#7201a8"
|
| 716 |
+
],
|
| 717 |
+
[
|
| 718 |
+
0.3333333333333333,
|
| 719 |
+
"#9c179e"
|
| 720 |
+
],
|
| 721 |
+
[
|
| 722 |
+
0.4444444444444444,
|
| 723 |
+
"#bd3786"
|
| 724 |
+
],
|
| 725 |
+
[
|
| 726 |
+
0.5555555555555556,
|
| 727 |
+
"#d8576b"
|
| 728 |
+
],
|
| 729 |
+
[
|
| 730 |
+
0.6666666666666666,
|
| 731 |
+
"#ed7953"
|
| 732 |
+
],
|
| 733 |
+
[
|
| 734 |
+
0.7777777777777778,
|
| 735 |
+
"#fb9f3a"
|
| 736 |
+
],
|
| 737 |
+
[
|
| 738 |
+
0.8888888888888888,
|
| 739 |
+
"#fdca26"
|
| 740 |
+
],
|
| 741 |
+
[
|
| 742 |
+
1,
|
| 743 |
+
"#f0f921"
|
| 744 |
+
]
|
| 745 |
+
],
|
| 746 |
+
"type": "heatmap"
|
| 747 |
+
}
|
| 748 |
+
],
|
| 749 |
+
"histogram": [
|
| 750 |
+
{
|
| 751 |
+
"marker": {
|
| 752 |
+
"pattern": {
|
| 753 |
+
"fillmode": "overlay",
|
| 754 |
+
"size": 10,
|
| 755 |
+
"solidity": 0.2
|
| 756 |
+
}
|
| 757 |
+
},
|
| 758 |
+
"type": "histogram"
|
| 759 |
+
}
|
| 760 |
+
],
|
| 761 |
+
"histogram2d": [
|
| 762 |
+
{
|
| 763 |
+
"colorbar": {
|
| 764 |
+
"outlinewidth": 0,
|
| 765 |
+
"ticks": ""
|
| 766 |
+
},
|
| 767 |
+
"colorscale": [
|
| 768 |
+
[
|
| 769 |
+
0,
|
| 770 |
+
"#0d0887"
|
| 771 |
+
],
|
| 772 |
+
[
|
| 773 |
+
0.1111111111111111,
|
| 774 |
+
"#46039f"
|
| 775 |
+
],
|
| 776 |
+
[
|
| 777 |
+
0.2222222222222222,
|
| 778 |
+
"#7201a8"
|
| 779 |
+
],
|
| 780 |
+
[
|
| 781 |
+
0.3333333333333333,
|
| 782 |
+
"#9c179e"
|
| 783 |
+
],
|
| 784 |
+
[
|
| 785 |
+
0.4444444444444444,
|
| 786 |
+
"#bd3786"
|
| 787 |
+
],
|
| 788 |
+
[
|
| 789 |
+
0.5555555555555556,
|
| 790 |
+
"#d8576b"
|
| 791 |
+
],
|
| 792 |
+
[
|
| 793 |
+
0.6666666666666666,
|
| 794 |
+
"#ed7953"
|
| 795 |
+
],
|
| 796 |
+
[
|
| 797 |
+
0.7777777777777778,
|
| 798 |
+
"#fb9f3a"
|
| 799 |
+
],
|
| 800 |
+
[
|
| 801 |
+
0.8888888888888888,
|
| 802 |
+
"#fdca26"
|
| 803 |
+
],
|
| 804 |
+
[
|
| 805 |
+
1,
|
| 806 |
+
"#f0f921"
|
| 807 |
+
]
|
| 808 |
+
],
|
| 809 |
+
"type": "histogram2d"
|
| 810 |
+
}
|
| 811 |
+
],
|
| 812 |
+
"histogram2dcontour": [
|
| 813 |
+
{
|
| 814 |
+
"colorbar": {
|
| 815 |
+
"outlinewidth": 0,
|
| 816 |
+
"ticks": ""
|
| 817 |
+
},
|
| 818 |
+
"colorscale": [
|
| 819 |
+
[
|
| 820 |
+
0,
|
| 821 |
+
"#0d0887"
|
| 822 |
+
],
|
| 823 |
+
[
|
| 824 |
+
0.1111111111111111,
|
| 825 |
+
"#46039f"
|
| 826 |
+
],
|
| 827 |
+
[
|
| 828 |
+
0.2222222222222222,
|
| 829 |
+
"#7201a8"
|
| 830 |
+
],
|
| 831 |
+
[
|
| 832 |
+
0.3333333333333333,
|
| 833 |
+
"#9c179e"
|
| 834 |
+
],
|
| 835 |
+
[
|
| 836 |
+
0.4444444444444444,
|
| 837 |
+
"#bd3786"
|
| 838 |
+
],
|
| 839 |
+
[
|
| 840 |
+
0.5555555555555556,
|
| 841 |
+
"#d8576b"
|
| 842 |
+
],
|
| 843 |
+
[
|
| 844 |
+
0.6666666666666666,
|
| 845 |
+
"#ed7953"
|
| 846 |
+
],
|
| 847 |
+
[
|
| 848 |
+
0.7777777777777778,
|
| 849 |
+
"#fb9f3a"
|
| 850 |
+
],
|
| 851 |
+
[
|
| 852 |
+
0.8888888888888888,
|
| 853 |
+
"#fdca26"
|
| 854 |
+
],
|
| 855 |
+
[
|
| 856 |
+
1,
|
| 857 |
+
"#f0f921"
|
| 858 |
+
]
|
| 859 |
+
],
|
| 860 |
+
"type": "histogram2dcontour"
|
| 861 |
+
}
|
| 862 |
+
],
|
| 863 |
+
"mesh3d": [
|
| 864 |
+
{
|
| 865 |
+
"colorbar": {
|
| 866 |
+
"outlinewidth": 0,
|
| 867 |
+
"ticks": ""
|
| 868 |
+
},
|
| 869 |
+
"type": "mesh3d"
|
| 870 |
+
}
|
| 871 |
+
],
|
| 872 |
+
"parcoords": [
|
| 873 |
+
{
|
| 874 |
+
"line": {
|
| 875 |
+
"colorbar": {
|
| 876 |
+
"outlinewidth": 0,
|
| 877 |
+
"ticks": ""
|
| 878 |
+
}
|
| 879 |
+
},
|
| 880 |
+
"type": "parcoords"
|
| 881 |
+
}
|
| 882 |
+
],
|
| 883 |
+
"pie": [
|
| 884 |
+
{
|
| 885 |
+
"automargin": true,
|
| 886 |
+
"type": "pie"
|
| 887 |
+
}
|
| 888 |
+
],
|
| 889 |
+
"scatter": [
|
| 890 |
+
{
|
| 891 |
+
"fillpattern": {
|
| 892 |
+
"fillmode": "overlay",
|
| 893 |
+
"size": 10,
|
| 894 |
+
"solidity": 0.2
|
| 895 |
+
},
|
| 896 |
+
"type": "scatter"
|
| 897 |
+
}
|
| 898 |
+
],
|
| 899 |
+
"scatter3d": [
|
| 900 |
+
{
|
| 901 |
+
"line": {
|
| 902 |
+
"colorbar": {
|
| 903 |
+
"outlinewidth": 0,
|
| 904 |
+
"ticks": ""
|
| 905 |
+
}
|
| 906 |
+
},
|
| 907 |
+
"marker": {
|
| 908 |
+
"colorbar": {
|
| 909 |
+
"outlinewidth": 0,
|
| 910 |
+
"ticks": ""
|
| 911 |
+
}
|
| 912 |
+
},
|
| 913 |
+
"type": "scatter3d"
|
| 914 |
+
}
|
| 915 |
+
],
|
| 916 |
+
"scattercarpet": [
|
| 917 |
+
{
|
| 918 |
+
"marker": {
|
| 919 |
+
"colorbar": {
|
| 920 |
+
"outlinewidth": 0,
|
| 921 |
+
"ticks": ""
|
| 922 |
+
}
|
| 923 |
+
},
|
| 924 |
+
"type": "scattercarpet"
|
| 925 |
+
}
|
| 926 |
+
],
|
| 927 |
+
"scattergeo": [
|
| 928 |
+
{
|
| 929 |
+
"marker": {
|
| 930 |
+
"colorbar": {
|
| 931 |
+
"outlinewidth": 0,
|
| 932 |
+
"ticks": ""
|
| 933 |
+
}
|
| 934 |
+
},
|
| 935 |
+
"type": "scattergeo"
|
| 936 |
+
}
|
| 937 |
+
],
|
| 938 |
+
"scattergl": [
|
| 939 |
+
{
|
| 940 |
+
"marker": {
|
| 941 |
+
"colorbar": {
|
| 942 |
+
"outlinewidth": 0,
|
| 943 |
+
"ticks": ""
|
| 944 |
+
}
|
| 945 |
+
},
|
| 946 |
+
"type": "scattergl"
|
| 947 |
+
}
|
| 948 |
+
],
|
| 949 |
+
"scattermap": [
|
| 950 |
+
{
|
| 951 |
+
"marker": {
|
| 952 |
+
"colorbar": {
|
| 953 |
+
"outlinewidth": 0,
|
| 954 |
+
"ticks": ""
|
| 955 |
+
}
|
| 956 |
+
},
|
| 957 |
+
"type": "scattermap"
|
| 958 |
+
}
|
| 959 |
+
],
|
| 960 |
+
"scattermapbox": [
|
| 961 |
+
{
|
| 962 |
+
"marker": {
|
| 963 |
+
"colorbar": {
|
| 964 |
+
"outlinewidth": 0,
|
| 965 |
+
"ticks": ""
|
| 966 |
+
}
|
| 967 |
+
},
|
| 968 |
+
"type": "scattermapbox"
|
| 969 |
+
}
|
| 970 |
+
],
|
| 971 |
+
"scatterpolar": [
|
| 972 |
+
{
|
| 973 |
+
"marker": {
|
| 974 |
+
"colorbar": {
|
| 975 |
+
"outlinewidth": 0,
|
| 976 |
+
"ticks": ""
|
| 977 |
+
}
|
| 978 |
+
},
|
| 979 |
+
"type": "scatterpolar"
|
| 980 |
+
}
|
| 981 |
+
],
|
| 982 |
+
"scatterpolargl": [
|
| 983 |
+
{
|
| 984 |
+
"marker": {
|
| 985 |
+
"colorbar": {
|
| 986 |
+
"outlinewidth": 0,
|
| 987 |
+
"ticks": ""
|
| 988 |
+
}
|
| 989 |
+
},
|
| 990 |
+
"type": "scatterpolargl"
|
| 991 |
+
}
|
| 992 |
+
],
|
| 993 |
+
"scatterternary": [
|
| 994 |
+
{
|
| 995 |
+
"marker": {
|
| 996 |
+
"colorbar": {
|
| 997 |
+
"outlinewidth": 0,
|
| 998 |
+
"ticks": ""
|
| 999 |
+
}
|
| 1000 |
+
},
|
| 1001 |
+
"type": "scatterternary"
|
| 1002 |
+
}
|
| 1003 |
+
],
|
| 1004 |
+
"surface": [
|
| 1005 |
+
{
|
| 1006 |
+
"colorbar": {
|
| 1007 |
+
"outlinewidth": 0,
|
| 1008 |
+
"ticks": ""
|
| 1009 |
+
},
|
| 1010 |
+
"colorscale": [
|
| 1011 |
+
[
|
| 1012 |
+
0,
|
| 1013 |
+
"#0d0887"
|
| 1014 |
+
],
|
| 1015 |
+
[
|
| 1016 |
+
0.1111111111111111,
|
| 1017 |
+
"#46039f"
|
| 1018 |
+
],
|
| 1019 |
+
[
|
| 1020 |
+
0.2222222222222222,
|
| 1021 |
+
"#7201a8"
|
| 1022 |
+
],
|
| 1023 |
+
[
|
| 1024 |
+
0.3333333333333333,
|
| 1025 |
+
"#9c179e"
|
| 1026 |
+
],
|
| 1027 |
+
[
|
| 1028 |
+
0.4444444444444444,
|
| 1029 |
+
"#bd3786"
|
| 1030 |
+
],
|
| 1031 |
+
[
|
| 1032 |
+
0.5555555555555556,
|
| 1033 |
+
"#d8576b"
|
| 1034 |
+
],
|
| 1035 |
+
[
|
| 1036 |
+
0.6666666666666666,
|
| 1037 |
+
"#ed7953"
|
| 1038 |
+
],
|
| 1039 |
+
[
|
| 1040 |
+
0.7777777777777778,
|
| 1041 |
+
"#fb9f3a"
|
| 1042 |
+
],
|
| 1043 |
+
[
|
| 1044 |
+
0.8888888888888888,
|
| 1045 |
+
"#fdca26"
|
| 1046 |
+
],
|
| 1047 |
+
[
|
| 1048 |
+
1,
|
| 1049 |
+
"#f0f921"
|
| 1050 |
+
]
|
| 1051 |
+
],
|
| 1052 |
+
"type": "surface"
|
| 1053 |
+
}
|
| 1054 |
+
],
|
| 1055 |
+
"table": [
|
| 1056 |
+
{
|
| 1057 |
+
"cells": {
|
| 1058 |
+
"fill": {
|
| 1059 |
+
"color": "#EBF0F8"
|
| 1060 |
+
},
|
| 1061 |
+
"line": {
|
| 1062 |
+
"color": "white"
|
| 1063 |
+
}
|
| 1064 |
+
},
|
| 1065 |
+
"header": {
|
| 1066 |
+
"fill": {
|
| 1067 |
+
"color": "#C8D4E3"
|
| 1068 |
+
},
|
| 1069 |
+
"line": {
|
| 1070 |
+
"color": "white"
|
| 1071 |
+
}
|
| 1072 |
+
},
|
| 1073 |
+
"type": "table"
|
| 1074 |
+
}
|
| 1075 |
+
]
|
| 1076 |
+
},
|
| 1077 |
+
"layout": {
|
| 1078 |
+
"annotationdefaults": {
|
| 1079 |
+
"arrowcolor": "#2a3f5f",
|
| 1080 |
+
"arrowhead": 0,
|
| 1081 |
+
"arrowwidth": 1
|
| 1082 |
+
},
|
| 1083 |
+
"autotypenumbers": "strict",
|
| 1084 |
+
"coloraxis": {
|
| 1085 |
+
"colorbar": {
|
| 1086 |
+
"outlinewidth": 0,
|
| 1087 |
+
"ticks": ""
|
| 1088 |
+
}
|
| 1089 |
+
},
|
| 1090 |
+
"colorscale": {
|
| 1091 |
+
"diverging": [
|
| 1092 |
+
[
|
| 1093 |
+
0,
|
| 1094 |
+
"#8e0152"
|
| 1095 |
+
],
|
| 1096 |
+
[
|
| 1097 |
+
0.1,
|
| 1098 |
+
"#c51b7d"
|
| 1099 |
+
],
|
| 1100 |
+
[
|
| 1101 |
+
0.2,
|
| 1102 |
+
"#de77ae"
|
| 1103 |
+
],
|
| 1104 |
+
[
|
| 1105 |
+
0.3,
|
| 1106 |
+
"#f1b6da"
|
| 1107 |
+
],
|
| 1108 |
+
[
|
| 1109 |
+
0.4,
|
| 1110 |
+
"#fde0ef"
|
| 1111 |
+
],
|
| 1112 |
+
[
|
| 1113 |
+
0.5,
|
| 1114 |
+
"#f7f7f7"
|
| 1115 |
+
],
|
| 1116 |
+
[
|
| 1117 |
+
0.6,
|
| 1118 |
+
"#e6f5d0"
|
| 1119 |
+
],
|
| 1120 |
+
[
|
| 1121 |
+
0.7,
|
| 1122 |
+
"#b8e186"
|
| 1123 |
+
],
|
| 1124 |
+
[
|
| 1125 |
+
0.8,
|
| 1126 |
+
"#7fbc41"
|
| 1127 |
+
],
|
| 1128 |
+
[
|
| 1129 |
+
0.9,
|
| 1130 |
+
"#4d9221"
|
| 1131 |
+
],
|
| 1132 |
+
[
|
| 1133 |
+
1,
|
| 1134 |
+
"#276419"
|
| 1135 |
+
]
|
| 1136 |
+
],
|
| 1137 |
+
"sequential": [
|
| 1138 |
+
[
|
| 1139 |
+
0,
|
| 1140 |
+
"#0d0887"
|
| 1141 |
+
],
|
| 1142 |
+
[
|
| 1143 |
+
0.1111111111111111,
|
| 1144 |
+
"#46039f"
|
| 1145 |
+
],
|
| 1146 |
+
[
|
| 1147 |
+
0.2222222222222222,
|
| 1148 |
+
"#7201a8"
|
| 1149 |
+
],
|
| 1150 |
+
[
|
| 1151 |
+
0.3333333333333333,
|
| 1152 |
+
"#9c179e"
|
| 1153 |
+
],
|
| 1154 |
+
[
|
| 1155 |
+
0.4444444444444444,
|
| 1156 |
+
"#bd3786"
|
| 1157 |
+
],
|
| 1158 |
+
[
|
| 1159 |
+
0.5555555555555556,
|
| 1160 |
+
"#d8576b"
|
| 1161 |
+
],
|
| 1162 |
+
[
|
| 1163 |
+
0.6666666666666666,
|
| 1164 |
+
"#ed7953"
|
| 1165 |
+
],
|
| 1166 |
+
[
|
| 1167 |
+
0.7777777777777778,
|
| 1168 |
+
"#fb9f3a"
|
| 1169 |
+
],
|
| 1170 |
+
[
|
| 1171 |
+
0.8888888888888888,
|
| 1172 |
+
"#fdca26"
|
| 1173 |
+
],
|
| 1174 |
+
[
|
| 1175 |
+
1,
|
| 1176 |
+
"#f0f921"
|
| 1177 |
+
]
|
| 1178 |
+
],
|
| 1179 |
+
"sequentialminus": [
|
| 1180 |
+
[
|
| 1181 |
+
0,
|
| 1182 |
+
"#0d0887"
|
| 1183 |
+
],
|
| 1184 |
+
[
|
| 1185 |
+
0.1111111111111111,
|
| 1186 |
+
"#46039f"
|
| 1187 |
+
],
|
| 1188 |
+
[
|
| 1189 |
+
0.2222222222222222,
|
| 1190 |
+
"#7201a8"
|
| 1191 |
+
],
|
| 1192 |
+
[
|
| 1193 |
+
0.3333333333333333,
|
| 1194 |
+
"#9c179e"
|
| 1195 |
+
],
|
| 1196 |
+
[
|
| 1197 |
+
0.4444444444444444,
|
| 1198 |
+
"#bd3786"
|
| 1199 |
+
],
|
| 1200 |
+
[
|
| 1201 |
+
0.5555555555555556,
|
| 1202 |
+
"#d8576b"
|
| 1203 |
+
],
|
| 1204 |
+
[
|
| 1205 |
+
0.6666666666666666,
|
| 1206 |
+
"#ed7953"
|
| 1207 |
+
],
|
| 1208 |
+
[
|
| 1209 |
+
0.7777777777777778,
|
| 1210 |
+
"#fb9f3a"
|
| 1211 |
+
],
|
| 1212 |
+
[
|
| 1213 |
+
0.8888888888888888,
|
| 1214 |
+
"#fdca26"
|
| 1215 |
+
],
|
| 1216 |
+
[
|
| 1217 |
+
1,
|
| 1218 |
+
"#f0f921"
|
| 1219 |
+
]
|
| 1220 |
+
]
|
| 1221 |
+
},
|
| 1222 |
+
"colorway": [
|
| 1223 |
+
"#636efa",
|
| 1224 |
+
"#EF553B",
|
| 1225 |
+
"#00cc96",
|
| 1226 |
+
"#ab63fa",
|
| 1227 |
+
"#FFA15A",
|
| 1228 |
+
"#19d3f3",
|
| 1229 |
+
"#FF6692",
|
| 1230 |
+
"#B6E880",
|
| 1231 |
+
"#FF97FF",
|
| 1232 |
+
"#FECB52"
|
| 1233 |
+
],
|
| 1234 |
+
"font": {
|
| 1235 |
+
"color": "#2a3f5f"
|
| 1236 |
+
},
|
| 1237 |
+
"geo": {
|
| 1238 |
+
"bgcolor": "white",
|
| 1239 |
+
"lakecolor": "white",
|
| 1240 |
+
"landcolor": "white",
|
| 1241 |
+
"showlakes": true,
|
| 1242 |
+
"showland": true,
|
| 1243 |
+
"subunitcolor": "#C8D4E3"
|
| 1244 |
+
},
|
| 1245 |
+
"hoverlabel": {
|
| 1246 |
+
"align": "left"
|
| 1247 |
+
},
|
| 1248 |
+
"hovermode": "closest",
|
| 1249 |
+
"mapbox": {
|
| 1250 |
+
"style": "light"
|
| 1251 |
+
},
|
| 1252 |
+
"paper_bgcolor": "white",
|
| 1253 |
+
"plot_bgcolor": "white",
|
| 1254 |
+
"polar": {
|
| 1255 |
+
"angularaxis": {
|
| 1256 |
+
"gridcolor": "#EBF0F8",
|
| 1257 |
+
"linecolor": "#EBF0F8",
|
| 1258 |
+
"ticks": ""
|
| 1259 |
+
},
|
| 1260 |
+
"bgcolor": "white",
|
| 1261 |
+
"radialaxis": {
|
| 1262 |
+
"gridcolor": "#EBF0F8",
|
| 1263 |
+
"linecolor": "#EBF0F8",
|
| 1264 |
+
"ticks": ""
|
| 1265 |
+
}
|
| 1266 |
+
},
|
| 1267 |
+
"scene": {
|
| 1268 |
+
"xaxis": {
|
| 1269 |
+
"backgroundcolor": "white",
|
| 1270 |
+
"gridcolor": "#DFE8F3",
|
| 1271 |
+
"gridwidth": 2,
|
| 1272 |
+
"linecolor": "#EBF0F8",
|
| 1273 |
+
"showbackground": true,
|
| 1274 |
+
"ticks": "",
|
| 1275 |
+
"zerolinecolor": "#EBF0F8"
|
| 1276 |
+
},
|
| 1277 |
+
"yaxis": {
|
| 1278 |
+
"backgroundcolor": "white",
|
| 1279 |
+
"gridcolor": "#DFE8F3",
|
| 1280 |
+
"gridwidth": 2,
|
| 1281 |
+
"linecolor": "#EBF0F8",
|
| 1282 |
+
"showbackground": true,
|
| 1283 |
+
"ticks": "",
|
| 1284 |
+
"zerolinecolor": "#EBF0F8"
|
| 1285 |
+
},
|
| 1286 |
+
"zaxis": {
|
| 1287 |
+
"backgroundcolor": "white",
|
| 1288 |
+
"gridcolor": "#DFE8F3",
|
| 1289 |
+
"gridwidth": 2,
|
| 1290 |
+
"linecolor": "#EBF0F8",
|
| 1291 |
+
"showbackground": true,
|
| 1292 |
+
"ticks": "",
|
| 1293 |
+
"zerolinecolor": "#EBF0F8"
|
| 1294 |
+
}
|
| 1295 |
+
},
|
| 1296 |
+
"shapedefaults": {
|
| 1297 |
+
"line": {
|
| 1298 |
+
"color": "#2a3f5f"
|
| 1299 |
+
}
|
| 1300 |
+
},
|
| 1301 |
+
"ternary": {
|
| 1302 |
+
"aaxis": {
|
| 1303 |
+
"gridcolor": "#DFE8F3",
|
| 1304 |
+
"linecolor": "#A2B1C6",
|
| 1305 |
+
"ticks": ""
|
| 1306 |
+
},
|
| 1307 |
+
"baxis": {
|
| 1308 |
+
"gridcolor": "#DFE8F3",
|
| 1309 |
+
"linecolor": "#A2B1C6",
|
| 1310 |
+
"ticks": ""
|
| 1311 |
+
},
|
| 1312 |
+
"bgcolor": "white",
|
| 1313 |
+
"caxis": {
|
| 1314 |
+
"gridcolor": "#DFE8F3",
|
| 1315 |
+
"linecolor": "#A2B1C6",
|
| 1316 |
+
"ticks": ""
|
| 1317 |
+
}
|
| 1318 |
+
},
|
| 1319 |
+
"title": {
|
| 1320 |
+
"x": 0.05
|
| 1321 |
+
},
|
| 1322 |
+
"xaxis": {
|
| 1323 |
+
"automargin": true,
|
| 1324 |
+
"gridcolor": "#EBF0F8",
|
| 1325 |
+
"linecolor": "#EBF0F8",
|
| 1326 |
+
"ticks": "",
|
| 1327 |
+
"title": {
|
| 1328 |
+
"standoff": 15
|
| 1329 |
+
},
|
| 1330 |
+
"zerolinecolor": "#EBF0F8",
|
| 1331 |
+
"zerolinewidth": 2
|
| 1332 |
+
},
|
| 1333 |
+
"yaxis": {
|
| 1334 |
+
"automargin": true,
|
| 1335 |
+
"gridcolor": "#EBF0F8",
|
| 1336 |
+
"linecolor": "#EBF0F8",
|
| 1337 |
+
"ticks": "",
|
| 1338 |
+
"title": {
|
| 1339 |
+
"standoff": 15
|
| 1340 |
+
},
|
| 1341 |
+
"zerolinecolor": "#EBF0F8",
|
| 1342 |
+
"zerolinewidth": 2
|
| 1343 |
+
}
|
| 1344 |
+
}
|
| 1345 |
+
},
|
| 1346 |
+
"title": {
|
| 1347 |
+
"text": "<b>Rate Equity by Ownership Model:</b> Residential Premium Over Industrial Rates"
|
| 1348 |
+
},
|
| 1349 |
+
"xaxis": {
|
| 1350 |
+
"anchor": "y",
|
| 1351 |
+
"categoryarray": [
|
| 1352 |
+
"Retail Power Marketer",
|
| 1353 |
+
"Municipal",
|
| 1354 |
+
"Investor Owned",
|
| 1355 |
+
"Cooperative",
|
| 1356 |
+
"State",
|
| 1357 |
+
"Behind the Meter"
|
| 1358 |
+
],
|
| 1359 |
+
"categoryorder": "array",
|
| 1360 |
+
"domain": [
|
| 1361 |
+
0,
|
| 1362 |
+
1
|
| 1363 |
+
],
|
| 1364 |
+
"title": {
|
| 1365 |
+
"text": "Type"
|
| 1366 |
+
}
|
| 1367 |
+
},
|
| 1368 |
+
"yaxis": {
|
| 1369 |
+
"anchor": "x",
|
| 1370 |
+
"domain": [
|
| 1371 |
+
0,
|
| 1372 |
+
1
|
| 1373 |
+
],
|
| 1374 |
+
"title": {
|
| 1375 |
+
"text": "Residential Premium ($/MWh)"
|
| 1376 |
+
}
|
| 1377 |
+
}
|
| 1378 |
+
}
|
| 1379 |
+
}
|
| 1380 |
+
},
|
| 1381 |
+
"metadata": {},
|
| 1382 |
+
"output_type": "display_data"
|
| 1383 |
+
}
|
| 1384 |
+
],
|
| 1385 |
+
"source": [
|
| 1386 |
+
"strip_plot = plot_util.get_price_spread_strip_plot(ny_df)\n",
|
| 1387 |
+
"\n",
|
| 1388 |
+
"strip_plot.show()"
|
| 1389 |
+
]
|
| 1390 |
+
},
|
| 1391 |
+
{
|
| 1392 |
+
"cell_type": "markdown",
|
| 1393 |
+
"id": "99326360",
|
| 1394 |
+
"metadata": {},
|
| 1395 |
+
"source": [
|
| 1396 |
+
"**Interpretation:** Investor-owned utilities dominate rates on the high end, but the spread within each ownership category is wide enough that ownership type alone doesn't fully explain the disparity. There's meaningful variation within ownership types worth investigating."
|
| 1397 |
+
]
|
| 1398 |
+
},
|
| 1399 |
+
{
|
| 1400 |
+
"cell_type": "markdown",
|
| 1401 |
+
"id": "42f0d824",
|
| 1402 |
+
"metadata": {},
|
| 1403 |
+
"source": [
|
| 1404 |
+
"---\n",
|
| 1405 |
+
"## 4. Correlation Analysis\n",
|
| 1406 |
+
"\n",
|
| 1407 |
+
"Before building directional charts, we compute a Pearson correlation matrix across all key metrics. This serves two purposes: it quantifies the *strength and direction* of every pairwise relationship, and it surfaces any unexpected correlations that warrant further investigation. A positive correlation between `SystemLossPercentage` and `ResidentialUnitPrice` would support the hypothesis. A negative correlation between `LoadFactor` and `ResidentialUnitPrice` (higher efficiency → lower price) would also support it."
|
| 1408 |
+
]
|
| 1409 |
+
},
|
| 1410 |
+
{
|
| 1411 |
+
"cell_type": "code",
|
| 1412 |
+
"execution_count": 5,
|
| 1413 |
+
"id": "6e0480c9",
|
| 1414 |
+
"metadata": {},
|
| 1415 |
+
"outputs": [],
|
| 1416 |
+
"source": [
|
| 1417 |
+
"# Key metrics: System Loss %, Load Factor', Industrial Revenue %, Price Spread\n",
|
| 1418 |
+
"heatmap = plot_util.get_key_metrics_corr_matrix(ny_df)\n",
|
| 1419 |
+
"\n",
|
| 1420 |
+
"# heatmap.show()"
|
| 1421 |
+
]
|
| 1422 |
+
},
|
| 1423 |
+
{
|
| 1424 |
+
"cell_type": "markdown",
|
| 1425 |
+
"id": "0003dd2d",
|
| 1426 |
+
"metadata": {},
|
| 1427 |
+
"source": [
|
| 1428 |
+
"**Interpretation:** The heatmap gives us a first look at which efficiency metrics are most predictive of residential pricing. The strong negative correlation between `LoadFactor` and both `ResidentialUnitPrice` and PriceSpread stands out immediately — it suggests infrastructure utilization is a meaningful driver of what residential customers pay. `SystemLossPercentage` shows a weaker positive relationship, which we'll test directly in the next section."
|
| 1429 |
+
]
|
| 1430 |
+
},
|
| 1431 |
+
{
|
| 1432 |
+
"cell_type": "markdown",
|
| 1433 |
+
"id": "c92e34ee",
|
| 1434 |
+
"metadata": {},
|
| 1435 |
+
"source": [
|
| 1436 |
+
"---\n",
|
| 1437 |
+
"## 5. Fairness Audit — Efficiency vs. Residential Price\n",
|
| 1438 |
+
"\n",
|
| 1439 |
+
"This is the central test of the research question, presented as two side-by-side scatter plots sharing a y-axis (residential price). Each plot approaches inefficiency from a different angle:\n",
|
| 1440 |
+
"\n",
|
| 1441 |
+
"| Plot | X-axis | What it tests |\n",
|
| 1442 |
+
"|---|---|---|\n",
|
| 1443 |
+
"| Left | `SystemLossPercentage` | Does wasted energy in the grid cost residential customers more? |\n",
|
| 1444 |
+
"| Right | `LoadFactor` | Does underutilized infrastructure translate to higher per-MWh costs? |\n",
|
| 1445 |
+
"\n",
|
| 1446 |
+
"> **Filtering note:** This chart excludes utilities with zero system loss or zero load factor. These edge cases typically represent pass-through entities (pure resellers) or data reporting anomalies — including them would distort the OLS fit."
|
| 1447 |
+
]
|
| 1448 |
+
},
|
| 1449 |
+
{
|
| 1450 |
+
"cell_type": "code",
|
| 1451 |
+
"execution_count": 6,
|
| 1452 |
+
"id": "de2b920d",
|
| 1453 |
+
"metadata": {},
|
| 1454 |
+
"outputs": [],
|
| 1455 |
+
"source": [
|
| 1456 |
+
"# Keep residential utilities that are using energy (instead of ONLY reseale, etc.)\n",
|
| 1457 |
+
"residential_lf_sys_loss_df = data_util.get_residential_sys_loss(ny_df)\n",
|
| 1458 |
+
"residential_lf_sys_loss_df = data_util.get_residential_load_factor(\n",
|
| 1459 |
+
" residential_lf_sys_loss_df).round(2)\n",
|
| 1460 |
+
"\n",
|
| 1461 |
+
"dual_y_scatter = plot_util.get_fairness_dual_y_scatter_plot(\n",
|
| 1462 |
+
" residential_lf_sys_loss_df)\n",
|
| 1463 |
+
"\n",
|
| 1464 |
+
"# dual_y_scatter.show()"
|
| 1465 |
+
]
|
| 1466 |
+
},
|
| 1467 |
+
{
|
| 1468 |
+
"cell_type": "markdown",
|
| 1469 |
+
"id": "ccdd7bc3",
|
| 1470 |
+
"metadata": {},
|
| 1471 |
+
"source": [
|
| 1472 |
+
"**Interpretation:** The results are asymmetric: load factor explains 41.7% of residential price variance (R² = 0.417, p < 0.001), a statistically strong relationship. System loss explains 10.8% (R² = 0.108, p = 0.016) — modest but significant. The core hypothesis holds, but the mechanism differs: it is primarily underutilized infrastructure, not transmission waste, that correlates with higher residential rates in New York's utility landscape."
|
| 1473 |
+
]
|
| 1474 |
+
},
|
| 1475 |
+
{
|
| 1476 |
+
"cell_type": "markdown",
|
| 1477 |
+
"id": "c69057f2",
|
| 1478 |
+
"metadata": {},
|
| 1479 |
+
"source": [
|
| 1480 |
+
"---\n",
|
| 1481 |
+
"## 6. Rate Disparity — Residential vs. Industrial\n",
|
| 1482 |
+
"\n",
|
| 1483 |
+
"Even if inefficiency drives prices up overall, the burden may not fall equally. This chart examines the **top 10 utilities by Price Spread** — the gap between what residential and industrial customers pay per MWh. Industrial customers typically negotiate volume discounts — some spread is expected. But when residential customers pay 2–3× the industrial rate at the same utility, it raises questions about whether the rate structure reflects true cost-of-service differences or something else. This chart identifies the specific utilities where that premium is most extreme."
|
| 1484 |
+
]
|
| 1485 |
+
},
|
| 1486 |
+
{
|
| 1487 |
+
"cell_type": "code",
|
| 1488 |
+
"execution_count": 7,
|
| 1489 |
+
"id": "38d3d584",
|
| 1490 |
+
"metadata": {},
|
| 1491 |
+
"outputs": [],
|
| 1492 |
+
"source": [
|
| 1493 |
+
"# Keep utilities that offer both industrial and residential services\n",
|
| 1494 |
+
"res_ind_customers_df = data_util.get_customer_utilities(ny_df).round(2)\n",
|
| 1495 |
+
"\n",
|
| 1496 |
+
"dumbbell = plot_util.get_rate_disparity_dumbbell_plot(res_ind_customers_df)\n",
|
| 1497 |
+
"\n",
|
| 1498 |
+
"# dumbbell.show()"
|
| 1499 |
+
]
|
| 1500 |
+
},
|
| 1501 |
+
{
|
| 1502 |
+
"cell_type": "markdown",
|
| 1503 |
+
"id": "86fe4d1f",
|
| 1504 |
+
"metadata": {},
|
| 1505 |
+
"source": [
|
| 1506 |
+
"**Interpretation:** Con Edison (Consolidated Edison) shows the largest residential–industrial spread in the dataset, charging residential customers roughly $233/MWh against a near-zero industrial rate. Eight of the top ten utilities by spread are investor-owned — consistent with the box plot finding that IOUs exhibit the widest pricing distributions. Notably, Con Edison also carries a high system loss percentage, placing it in the upper-right quadrant of the fairness audit scatter: both inefficient and inequitable in its rate structure."
|
| 1507 |
+
]
|
| 1508 |
+
},
|
| 1509 |
+
{
|
| 1510 |
+
"cell_type": "markdown",
|
| 1511 |
+
"id": "8e8ae178",
|
| 1512 |
+
"metadata": {},
|
| 1513 |
+
"source": [
|
| 1514 |
+
"---\n",
|
| 1515 |
+
"## 7. Energy Flow Analysis\n",
|
| 1516 |
+
"\n",
|
| 1517 |
+
"The Sankey diagrams provide operational context for the efficiency metrics computed above. Rather than a single number, they show the *full picture* of where a utility's energy comes from and where it goes. A wide `Losses` band at a utility with high residential prices is the inefficiency–cost story in one image. We can compare the utility-level Sankey against the U.S. national average to understand whether NY's profile is typical or anomalous."
|
| 1518 |
+
]
|
| 1519 |
+
},
|
| 1520 |
+
{
|
| 1521 |
+
"cell_type": "code",
|
| 1522 |
+
"execution_count": 8,
|
| 1523 |
+
"id": "0ae862c6",
|
| 1524 |
+
"metadata": {},
|
| 1525 |
+
"outputs": [],
|
| 1526 |
+
"source": [
|
| 1527 |
+
"# Look at a specific utility's energy usage/flow\n",
|
| 1528 |
+
"utility_usage = data_util.get_utility_usage(ny_df.sum(), level=\"State\")\n",
|
| 1529 |
+
"ny_sankey = plot_util.get_energy_use_sankey_plot(utility_usage)\n",
|
| 1530 |
+
"\n",
|
| 1531 |
+
"# ny_sankey.show()\n",
|
| 1532 |
+
"\n",
|
| 1533 |
+
"# Look at the entire country's energy usage/flow\n",
|
| 1534 |
+
"us_energy_flow = df.groupby([\"Utility.State\"]).sum().sum()[1:]\n",
|
| 1535 |
+
"us_energy_flow = data_util.get_utility_usage(us_energy_flow, level=\"US\")\n",
|
| 1536 |
+
"\n",
|
| 1537 |
+
"us_sankey = plot_util.get_energy_use_sankey_plot(us_energy_flow)\n",
|
| 1538 |
+
"\n",
|
| 1539 |
+
"# us_sankey.show()"
|
| 1540 |
+
]
|
| 1541 |
+
},
|
| 1542 |
+
{
|
| 1543 |
+
"cell_type": "markdown",
|
| 1544 |
+
"id": "09b80967",
|
| 1545 |
+
"metadata": {},
|
| 1546 |
+
"source": [
|
| 1547 |
+
"**Interpretation:** The NY state aggregate Sankey shows a Losses band of approximately 3.2% — compare this against the U.S. national aggregate (~5-6% depending on aggregation method) to establish whether New York's grid is more or less efficient than the national benchmark. For individual utilities, the Losses band translates the abstract SystemLossPercentage into an immediate visual — and directly contextualizes the weakly significant but real correlation observed in the fairness audit."
|
| 1548 |
+
]
|
| 1549 |
+
},
|
| 1550 |
+
{
|
| 1551 |
+
"cell_type": "markdown",
|
| 1552 |
+
"id": "570d133c",
|
| 1553 |
+
"metadata": {},
|
| 1554 |
+
"source": [
|
| 1555 |
+
"### 7b. Interactive Explorer — Individual Utility Flow"
|
| 1556 |
+
]
|
| 1557 |
+
},
|
| 1558 |
+
{
|
| 1559 |
+
"cell_type": "code",
|
| 1560 |
+
"execution_count": 9,
|
| 1561 |
+
"id": "7b0d840c",
|
| 1562 |
+
"metadata": {},
|
| 1563 |
+
"outputs": [],
|
| 1564 |
+
"source": [
|
| 1565 |
+
"# Sankey chart with a drop down energy flow for each individual utility\n",
|
| 1566 |
+
"utility_sankey_with_dropdown = plot_util.add_utility_dropdown(ny_sankey, df=ny_df)\n",
|
| 1567 |
+
"\n",
|
| 1568 |
+
"# utility_sankey_with_dropdown.show()"
|
| 1569 |
+
]
|
| 1570 |
+
},
|
| 1571 |
+
{
|
| 1572 |
+
"cell_type": "markdown",
|
| 1573 |
+
"id": "25a0456f",
|
| 1574 |
+
"metadata": {},
|
| 1575 |
+
"source": [
|
| 1576 |
+
"---\n",
|
| 1577 |
+
"## 8. Export\n",
|
| 1578 |
+
"\n",
|
| 1579 |
+
"Uncomment the following cell to export all charts as SVGs to the `/images` directory.\n",
|
| 1580 |
+
"\n",
|
| 1581 |
+
"> **Requires:** `kaleido` module"
|
| 1582 |
+
]
|
| 1583 |
+
},
|
| 1584 |
+
{
|
| 1585 |
+
"cell_type": "code",
|
| 1586 |
+
"execution_count": 10,
|
| 1587 |
+
"id": "cb116404",
|
| 1588 |
+
"metadata": {},
|
| 1589 |
+
"outputs": [],
|
| 1590 |
+
"source": [
|
| 1591 |
+
"# %pip install kaleido\n",
|
| 1592 |
+
"\n",
|
| 1593 |
+
"# # Gather all plots and export them as SVGs\n",
|
| 1594 |
+
"# plots = [top_variance_table, strip_plot, heatmap,\n",
|
| 1595 |
+
"# dual_y_scatter, dumbbell, ny_sankey, us_sankey]\n",
|
| 1596 |
+
"\n",
|
| 1597 |
+
"# plot_util.export_plots_as_svg(plots)"
|
| 1598 |
+
]
|
| 1599 |
+
}
|
| 1600 |
+
],
|
| 1601 |
+
"metadata": {
|
| 1602 |
+
"kernelspec": {
|
| 1603 |
+
"display_name": "Python 3",
|
| 1604 |
+
"language": "python",
|
| 1605 |
+
"name": "python3"
|
| 1606 |
+
},
|
| 1607 |
+
"language_info": {
|
| 1608 |
+
"codemirror_mode": {
|
| 1609 |
+
"name": "ipython",
|
| 1610 |
+
"version": 3
|
| 1611 |
+
},
|
| 1612 |
+
"file_extension": ".py",
|
| 1613 |
+
"mimetype": "text/x-python",
|
| 1614 |
+
"name": "python",
|
| 1615 |
+
"nbconvert_exporter": "python",
|
| 1616 |
+
"pygments_lexer": "ipython3",
|
| 1617 |
+
"version": "3.13.5"
|
| 1618 |
+
}
|
| 1619 |
+
},
|
| 1620 |
+
"nbformat": 4,
|
| 1621 |
+
"nbformat_minor": 5
|
| 1622 |
+
}
|