{"cells":[{"metadata":{},"cell_type":"markdown","source":"<span style=\"font-size: 20px; color: white; background-color: #008ABC\">(ignore this ⤴ warning about deleted datasets!)</span>\n\n# An Analytical Approach to NO<sub>2</sub> Emissions\n**TL;DR: I take an analytical approach, measuring a line density of NO2 on windy days at each point and fitting a distribution function to this line. This function gives the emissions in kg/hr. I use night time lights satellite imagery as a proxy for human activity to separate power and non-power emissions and then use the generation at each site with this value to calculate an emissions factor in kg/MWh.**\n\n## Overview\nWe need to calculate the emissions factor in kg/MWh (or some equivalent) averaged for one year of Puerto Rico's energy production.\nBonus points if we can calculate sub-annual and marginal emissions factors.\n\nSo we need to figure out the following system:\n![pr-system.png](attachment:pr-system.png)\n\nPuerto Rico's wind blows mainly from the east, so there won't be any pollution blown in. But the rest still need to be accounted for.\n\nThis problem definitely seems to want an analytical approach (rather than statistics/machine learning) so I'm going to go with that (and check if it makes sense along the way). In addition, I'm going to keep the data sources as simple as possible! There are several places where I could improve the accuracy by including more data from Google Earth Engine and other sources, but as this is a demonstration of a method, I'm going to keep it simple. For example, as I'm going to focus on the troposphere as a whole, I should probably use several wind fields that apply to the whole troposphere, instead of just the GFS data near the ground.\n\n## Emissions factor\nSimplifying from the [EPA formula](https://www.epa.gov/air-emissions-factors-and-quantification/basic-information-air-emissions-factors-and-quantification#About%20Emissions%20Factors):  \n*EF = E / G*\n\nwhere:\n- EF: emissions factor (kg/MWh)\n- E: emissions (kg/hr)\n- G: generation (MWh/hr)\n\nSo if I can determine the *emissions* that are attributable to electricity generation, and if I know the *generation*, then I can calculate the emissions factor!\n\nQuick explanation that may be useful: a plant's *capacity*, usually measured in MW (megawatts) is the maximum instantaneous power it can output, while it's *generation*, often in MWh (megawatt-hours) is the amount of energy actually produced in a given time period. If the plant runs at full capacity non-stop (i.e. capacity factor equals 100%) then the generation will be a simple multiple of capacity. Normally, however, plants produce much less, and have a capacity factor below 100%. It might be 30% for wind and solar, 50-60% for fossil fuels, and 80%+ for nuclear power.","attachments":{"pr-system.png":{"image/png":"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"}}},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":false},"cell_type":"code","source":"# Imports\nfrom pathlib import Path\nfrom pprint import pprint\nimport json\nfrom datetime import datetime\nfrom calendar import monthrange\nfrom math import sqrt, pi, exp, sin, cos\n\nimport numpy as np\nfrom scipy.optimize import curve_fit\nfrom scipy.interpolate import UnivariateSpline\nimport pandas as pd\nfrom shapely.geometry import Point\nimport geopandas as gpd\nimport matplotlib.pyplot as plt\nfrom matplotlib.colors import ListedColormap\nimport matplotlib.dates as mdates\nimport matplotlib.ticker as ticker\nimport seaborn as sns\nimport rasterio as rio\nfrom rasterstats import zonal_stats\nfrom affine import Affine\nfrom tqdm.notebook import tqdm\nimport folium\nimport branca\n\nsns.set() # For pretty charts","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":false},"cell_type":"code","source":"# Paths\nroot = Path(\"/kaggle/input/\")\nadmin_path = root / \"world-geo-data\" / \"ne_10m_admin_1_states_provinces.shp\"  # admin boundaries\nviirs_path = root / \"viirs-dnb-annual-puertorico\" / \"viirs-pr-2019.tif\"  # night lights\neia_path = root / \"eia923-pr-2018\" / \"EIA923_PR_2018.csv\"  # plant generation data\neie = root / \"ds4g-environmental-insights-explorer\" / \"eie_data\"\ns5p_path = eie / \"s5p_no2\"  # Sentinel 5P\ngldas_path = eie / \"gldas\"  # Land Data\ngfs_path = eie / \"gfs\"  # forecast\ngppd_path = eie / \"gppd\" / \"gppd_120_pr.csv\"  # power plants","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":false},"cell_type":"code","source":"# Load the administrative boundary\nadmin = gpd.read_file(admin_path)\nadmin = admin.loc[admin.name == \"Puerto Rico\"]","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Power System Overview\n## Before going any further, let's look at the power system\nThis data comes from the WRI's [Global Power Plant Database](https://developers.google.com/earth-engine/datasets/catalog/WRI_GPPD_power_plants), which has relatively detailed data on most of the power plants in the world.\n\nFun fact: I contributed some of the GPPD data and sources for Bolivia!"},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":false},"cell_type":"code","source":"fossil_fuels = [\n    \"Oil\",\n    \"Gas\",\n    \"Coal\"\n]","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-output":false,"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"def show_gppd_summary(gppd):\n    \"\"\"\n    Display a quick summary of power plants, broken down by fuel.\n    \"\"\"\n    \n    sns.set_style(\"whitegrid\")\n    tot_mw = gppd.capacity_mw.sum()\n    fossil_mw = gppd.loc[(gppd.primary_fuel.isin(fossil_fuels))].capacity_mw.sum()\n    print(f\"{100*fossil_mw/tot_mw:.0f}% of total capacity is fossil fuel-based\")\n\n    fuel_tots = gppd.groupby('primary_fuel')['capacity_mw'].sum().sort_values(ascending=False)\n\n    fig, ax = plt.subplots(figsize=(18,6))\n    fuel_tots.plot.bar(\n        ax=ax,\n        color=['gray', 'darkblue', 'black', 'orange','green','blue']\n    )\n    ax.set_title(\"Total capacity by fuel\", fontsize=16)\n    ax.set_ylabel(\"Total capacity (MW)\", fontsize=16)\n    ax.set_xlabel(\"Fuel\", fontsize=16)\n    ax.set_xlabel(\"Fuel\")\n    ax.xaxis.grid(False)\n    ax.set_ylim([0, 5000])\n    ax.tick_params(axis='both', which='both', labelsize=12)\n    sns.despine()\n    plt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"gppd = pd.read_csv(gppd_path)\nshow_gppd_summary(gppd)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## What does this tell us?\nAverage NO<sub>x</sub> emissions factor in the US is 0.36 kg/MWh ([source](https://www.eia.gov/electricity/state/unitedstates/index.php)). We should expect the value for Puerto Rico to be significantly higher, around 1 kg/MWh, as it relies much more on fossil fuels than the US as a whole.\n\nSome rough emissions factors by fuel ([source](https://www3.epa.gov/ttnchie1/conference/ei19/session10/gpouliot.pdf))\n- coal: 11 - 45 kg/MWh\n- oil: 1.6 - 3.2 kg/MWh\n- gas: 0.16 - 0.32 kg/MWh\n\nAs Puerto Rico mostly runs on oil, the emissions factor could be higher still.\n\nFor the majority of this investigation, I'm going to focus on these oil, gas and coal power plants, as these will be responsible for the bulk of pollution. I'll have to bring the others back in to get the total (and marginal!) emissions factors. For now, I'm going a step further, and only including the larger fossil fuel plants (above 150 MW), as these are the only ones likely to make a significant individual, measurable contribution to NO2 pollution. I'll assess these steps later to understand if I'm introducing any inaccuracies by simplifying like this!\n\nThat gives us the following plants:"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"min_capacity = 150\nprint(\"Fossil-fuel plants above 150 MW capacity:\")\nprint(\", \".join(gppd.loc[(gppd.primary_fuel.isin(fossil_fuels)) & (gppd.capacity_mw > min_capacity)][\"name\"].to_list()))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"I'm also going to have a look at reported monthly generation for 2018, using EIA data that was uncovered in [this discussion](https://www.kaggle.com/c/ds4g-environmental-insights-explorer/discussion/131395). This will be crucial to calculate the emissions factor, as the GPPD numbers are a bit suspect, and (as discussed above) capacity on its own isn't enough. The plant names in this dataset are slightly different from those above, so I manually put together the following mapping between the two plant-naming systems:"},{"metadata":{"_kg_hide-output":false,"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"key_plants = {\n    \"Cambalache\": \"Cambalache Plant\",\n    \"San Juan CC\": \"Central San Juan Plant\",\n    \"Palo Seco\": \"Palo Seco Plant\",\n    \"Mayagüez\": \"Mayaguez Plant\",\n    \"EcoEléctrica\": \"EcoElectrica\",\n    \"Costa Sur\": \"Costa Sur Plant\",\n    \"Aguirre\": \"Aguirre Plant\",\n    \"A.E.S. Corp.\": \"AES Puerto Rico\"\n}\nprint(\"GPPD name\\t\\tEIA name\")\nprint(\"-\" * 46)\nfor k, v in key_plants.items():\n    print(f\"{k}:\\t\\t{v}\")","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"_kg_hide-output":false,"trusted":true},"cell_type":"code","source":"def merge_power_data(eia, gppd):\n    \"\"\"\n    Merge together the main fossil fuel plant data from bata datasets.\n    \"\"\"\n    \n    eia = eia.dropna(axis=\"index\", how=\"any\", thresh=4)  # get rid of rows with too much NaN data\n    eia_gr = eia.groupby([\"Plant_Name\"]).sum()  # and combine plants with the same name (separate sub-units listed independently)\n    eia_gr[\"Fuel_Code\"] = eia.groupby([\"Plant_Name\"])[\"Fuel_Code\"].apply(lambda x: \", \".join(x))  # keeping all fuel codes\n    eia_gr = eia_gr.loc[eia_gr.index.isin(key_plants.values())]  # keep only main plants\n\n    # simplify GPPD data before joining\n    gppd_extra = gppd.groupby(\"name\").first()\n    gppd_join = gppd.groupby(\"name\").sum().reset_index()[[\"name\", \"capacity_mw\"]]\n    gppd_join[\".geo\"] = gppd_extra[\".geo\"].to_list()\n    gppd_join[\"primary_fuel\"] = gppd_extra[\"primary_fuel\"].to_list()\n    gppd_join = gppd_join.loc[gppd_join[\"name\"].isin(key_plants.keys())]\n    gppd_join[\"name_join\"] = gppd_join.apply(lambda row: key_plants[row[\"name\"]], axis=1)\n\n    # join on names\n    plants = eia_gr.merge(gppd_join, how=\"left\", left_on=\"Plant_Name\", right_on=\"name_join\")\n    plants = plants.set_index(\"name\").drop(columns=[\"name_join\"])\n    plants[\"xy\"] = plants[\".geo\"].apply(lambda geo: json.loads(geo)[\"coordinates\"])  # extract x, y coords\n    plants = plants.drop(columns=\".geo\")\n    plants[\"generation_hourly\"] = plants[\"Netgen_Total\"] / (24 * 365)  # convert from yearly to hourly\n    plants[\"capacity_factor\"] = plants[\"generation_hourly\"] / plants[\"capacity_mw\"]  # calc capacity factor\n    plants = gpd.GeoDataFrame(plants, geometry=plants[\"xy\"].map(lambda x: Point(x[0], x[1])))\n    \n    return plants","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"def pretty_cols(plants):\n    \"\"\"Create some prettier column names for displaying.\"\"\"\n    plants[\"Capacity (MW)\"] = plants[\"capacity_mw\"].astype(int)\n    plants[\"Hourly Generation (MWh)\"] = plants[\"generation_hourly\"].apply(lambda x: f\"{x:.0f}\")\n    plants[\"Capacity Factor\"] = plants[\"capacity_factor\"].apply(lambda x: f\"{100*x:.0f}%\")\n    plants[\"Primary Fuel\"] = plants[\"primary_fuel\"]\n    return plants","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Now that I'm able to calculate the capacity factors, and, as expected, they're all well below 100% (see below)! Good thing I didn't just rely on the max capacities of these plants."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"plants = pd.read_csv(eia_path)\nplants = merge_power_data(plants, gppd)\nplants = pretty_cols(plants)\nplants[[\"Primary Fuel\", \"Capacity (MW)\", \"Hourly Generation (MWh)\", \"Capacity Factor\"]]","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Before going on, let's just check how much of the power system I've excluded with our filtering steps."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"print(f\"Included plants account for {(100 * plants.capacity_mw.sum() / gppd.capacity_mw.sum()):.0f}% of the total system.\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Which I think is fine. I've included the bulk of power capacity in our analysis. The remaining part can be accounted for at the end, and will be within the margins of error in any case.\n\nImportant note: this power data is January - December of 2018, whereas the satellite and weather data is July 2018 - June 2019. I'm going to ignore this and assume October behaves like October and so on... but it would be a big improvement to use the correct atmospheric data for the correct generation data!"},{"metadata":{},"cell_type":"markdown","source":"## Great, now let's have a look at these on a map\nI've also included the administrative boundary of Puerto Rico in blue just so it's clear what I'm talking about!"},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"def map_plants(df):\n    \"\"\"\n    Draw a Folium map of GPPD data.\n    \"\"\"\n    \n    colors = {\n        \"Oil\": \"gray\",\n        \"Gas\": \"darkblue\",\n        \"Coal\": \"black\",\n        \"none\": \"green\"\n    }\n    \n    m = folium.Map(\n        location=(18.2, -66.3),\n        zoom_start=9,\n        tiles=\"Stamen Terrain\",\n        zoom_control=True,\n    )\n    \n    folium.features.GeoJson(\n        admin.to_json(),\n        style_function=lambda x: {'fillColor': '#FFFFFF', 'lineColor': '#00FFFFFF'},\n    ).add_to(m)\n    \n    for idx, row in df.iterrows():\n        lon, lat = row[\"xy\"]\n        icon = folium.Icon(\n            color=colors[row.primary_fuel.split(\",\")[0]],\n            icon_color=\"white\",\n            icon=\"bolt\",\n            prefix=\"fa\"\n        )\n        tooltip = f\"<p><strong>{row.name}</strong></p><p>Fuel: {row.primary_fuel}<p><p>Cap: {row.capacity_mw:.0f} MW</p><p>Gen: {row.generation_hourly:.0f} MWh/h</p>\"\n        folium.Marker(\n            (lat, lon),\n            color=colors[row.primary_fuel.split(\",\")[0]],\n            icon=icon,\n            tooltip=tooltip,\n        ).add_to(m)\n    return m","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":false},"cell_type":"code","source":"m = map_plants(plants)\nprint(\"You can mouse over the points to see their name and other details.\")\nm","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Exploring Total NO<sub>2</sub> Levels\n## Now let's have a look at a year in NO<sub>2</sub> levels\nThe NO<sub>2</sub> data comes from the [Sentinel-5 Precursor](https://developers.google.com/earth-engine/datasets/catalog/COPERNICUS_S5P_OFFL_L3_NO2) mission, which provides NO<sub>2</sub> columns in mol/m<sup>2</sup>. The readings are provided daily, although there will be some days with too much cloud cover to be useful.\n\nTo make it a bit more understandable, I'm going to convert from mols/m<sup>2</sup> to tons. To do this, I'll use the molar mass of NO<sub>2</sub> (46.006  g/mol [source](https://pubchem.ncbi.nlm.nih.gov/compound/Nitrogen-dioxide)) and the total surface area of Puerto Rico (9104 km2 [source](https://en.wikipedia.org/wiki/Puerto_Rico)).\n\nAlthough the satellite data includes stratospheric NO<sub>2</sub>, for this challenge we're mostly interested in *tropospheric* levels of NO<sub>2</sub> (the layer closest to the surface of the earth) as these will more directly indicate outputs from power stations and other sources."},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"def flipud(raster, affine):\n    \"\"\"\n    Flip raster N-S and fix affine.\n    Corrects upside down S5P data.\n    Adapted from here: https://github.com/perrygeo/python-rasterstats/issues/98#issuecomment-471136551\n    \"\"\"\n    \n    raster = np.flipud(raster)\n    affine = Affine(\n        affine.a,\n        affine.b,\n        affine.c,\n        affine.d,\n        -1 * affine.e,\n        affine.f + (affine.e * (raster.shape[0]-1))\n    )\n    return raster, affine","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"def calc_no2(arr, aff, admin):\n    \"\"\"\n    Get mean of mol/m2, mutiply by total area of PR.\n    Then convert to grams/tons.\n    \"\"\"\n    \n    no2_mol_mass = 46.006  # g/mol source: https://pubchem.ncbi.nlm.nih.gov/compound/Nitrogen-dioxide\n    total_area = 9104 * 1e6  # m^2 area of PR source: https://en.wikipedia.org/wiki/Puerto_Rico\n    no2_col_mean = zonal_stats(\n        admin,\n        arr,\n        affine=aff,\n        nodata=-999,\n    )[0][\"mean\"]\n    no2_mol = no2_col_mean * total_area\n    no2_g = no2_mol * no2_mol_mass\n    no2_ton = no2_g / 1e6\n    return no2_ton","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"def average_s5p_cf(f, band=7):\n    \"\"\"\n    Get S5P cloud-cover fraction.\n    \"\"\"\n    \n    with rio.open(f) as rd:\n        arr = rd.read(band)\n    return np.nanmean(arr)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"def calc_tot_no2(f, admin, band=1):\n    \"\"\"\n    Calculate total NO2 over an area.\n    \"\"\"\n    with rio.open(f) as rd:\n        arr = rd.read(band)\n        aff = rd.transform\n    arr, aff = flipud(arr, aff)\n    no2_ton = calc_no2(arr, aff, admin)\n    return no2_ton","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"def s5_to_df(s5p_path, admin, disable_tqdm=False):\n    \"\"\"\n    Create a DF with time-series S5P data for the main values.\n    Total NO2, strat, trop and CF.\n    \"\"\"\n    \n    s5p_data = []\n    for f in tqdm(list(s5p_path.iterdir()), disable=disable_tqdm):\n        s5p_data.append({\n            \"date\": f.stem.split(\"no2_\")[1].split(\"T\")[0],\n            \"trop_no2\": calc_tot_no2(f, admin, band=2),\n            \"strat_no2\": calc_tot_no2(f, admin, band=3),\n            \"cf\": average_s5p_cf(f),\n        })\n    s5p_df = pd.DataFrame(s5p_data)\n    s5p_df[\"date\"] = pd.to_datetime(s5p_df[\"date\"])\n    s5p_df = s5p_df.sort_values(by=\"date\")\n    s5p_df = s5p_df.set_index(\"date\")\n    return s5p_df","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"def plot_total_no2(s5p_df, max_cf=0.3):\n    \"\"\"\n    Create a plot of total NO2 levels.\n    \"\"\"\n    \n    sns.set_style(\"whitegrid\")\n    s5p_df_cloudfree = s5p_df.loc[s5p_df.cf < max_cf]\n    no2_strat_ts = s5p_df_cloudfree[[\"strat_no2\"]]\n    no2_trop_ts = s5p_df_cloudfree[[\"trop_no2\"]]\n    cf_ts = s5p_df[[\"cf\"]]\n\n    fig, ax1 = plt.subplots(figsize=(20,10))\n    no2_strat_ts.plot(ax=ax1)\n    no2_trop_ts.plot(ax=ax1, color=\"green\", linewidth=4)\n    ax1.set_xlabel(\"Date\", fontsize=16)\n    ax1.set_ylabel(\"Total NO2 (tons)\", color=\"green\", fontsize=16)\n    ax1.set_ylim([0, 25])\n    ax1.legend([\"Stratospheric\", \"Tropospheric\"], loc=\"upper left\", prop={'size': 16})\n    ax1.set_title(\"Annual variation in total NO2 levels\", fontsize=16)\n    ax1.xaxis.grid(False)\n    ax1.tick_params(axis='both', which='both', labelsize=12)\n\n    ax2 = ax1.twinx()\n    cf_ts.plot(\n        linewidth=0.1,\n        color=\"black\",\n        ax=ax2,\n        legend=True,\n    )\n    ax2.set_ylabel(\"Cloud cover fraction\", color=\"black\", fontsize=16)\n    ax2.set_ylim([0, 1])\n    ax2.legend([\"Cloud cover fraction\"], prop={'size': 16})\n\n    ax1.xaxis.set_major_formatter(mdates.DateFormatter('%b %Y'))\n    fig.autofmt_xdate()\n    sns.despine(left=True)\n    plt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":false},"cell_type":"code","source":"s5p_df = s5_to_df(s5p_path, admin)\nprint(\"This plot shows troposheric NO2 in green, with days above 30% cloudy excluded.\\nStratospheric NO2 and cloud fraction are included for information.\")\nplot_total_no2(s5p_df)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Now what?\nThere isn't a huge pattern in tropospheric NO<sub>2</sub>. So any hopes of correlating with activity levels at a large scale or something else are probably out the window. However, this was an average for the whole of Puerto Rico, so it's probably time to start looking at specific sites."},{"metadata":{},"cell_type":"markdown","source":"# Wind to the Rescue\n\nLet's first have a look at the capital, San Juan, which has two fossil fuel stations and loads of people driving around. Puerto Rico seems super windy, so before going any further with NO<sub>2</sub>, let's see how that will affect the results.\n\nThe wind data comes from the [Global Forecast System](https://developers.google.com/earth-engine/datasets/catalog/NOAA_GFS0P25), and there are forecasts every six hours for that moment (i.e. the model's forecast of the moment the model runs, which is the most accurate).\n\nThe GFS data provides many values (temperature, humidity, etc) but for now I'm interested in wind: the U component (east-west) and V component (north-south). In order to convert these to magnitude and direction, I'll follow [these](http://colaweb.gmu.edu/dev/clim301/lectures/wind/wind-uv) handy instructions.\n\n*magnitude = sqrt(u<sup>2</sup> + v<sup>2</sup>)*  \n*angle = [arctan2](https://en.wikipedia.org/wiki/Atan2)(u, v)*\n\nThen I need to get compass bearing (not mathematical angle) of the direction where the wind *comes from*:  \n*bearing = 270 - angle x 180/π*  \nand subtract 360 if the result is above 360!"},{"metadata":{"_kg_hide-output":false,"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"def rad2com(rad):\n    \"\"\"Convert mathematical polar radians to compass direction.\"\"\"\n    com = 90 - rad * 180/pi\n    com = com if com > 0 else com + 360\n    return com","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-output":false,"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"def com2rad(com):\n    \"\"\"Convert compass direction to mathematical polar radians.\"\"\"\n    rad = (90 - com) * pi / 180\n    rad = rad if rad > -pi else rad + 2 * pi\n    return rad","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"def get_wind_data_point(gfs_path, x=-66.75, y=18, disable_tqdm=False):\n    \"\"\"\n    Create wind time-series at a point.\n    Calcualte magnitude and math + compass direction.\n    \"\"\"\n    \n    gfs_data = []\n    for f in tqdm(list(gfs_path.iterdir()), disable=disable_tqdm):\n        date = f.stem.split(\"gfs_\")[1]\n        with rio.open(f) as rd:\n            u, v = next(rd.sample(xy=[(x, y)], indexes=[4, 5]))  # 4 and 5 are the u and v bands\n            gfs_data.append({\n                \"date\": datetime(int(date[:4]), int(date[4:6]), int(date[6:8]), int(date[8:])),\n                \"u\": u,\n                \"v\": v,\n            })\n    gfs_df = pd.DataFrame(gfs_data)\n    gfs_df = gfs_df.sort_values(by=\"date\")\n    gfs_df = gfs_df.set_index(\"date\")\n    gfs_df[\"mag\"] = np.sqrt(gfs_df.u ** 2 + gfs_df.v ** 2)\n    gfs_df[\"dir\"] = np.arctan2(-gfs_df.v, -gfs_df.u)  # negative so we get the FROM direction!\n    gfs_df[\"compass\"] = gfs_df[\"dir\"].map(rad2com)\n    return gfs_df","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"x, y = plants.loc[\"San Juan CC\"].xy\ngfs_df = get_wind_data_point(gfs_path, x=x, y=y)\nprint(\"Let's have a quick look at a sample of wind data from this point to make sure our maths made sense!\")\ngfs_df.head(5)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"It seems to... negative *u* is wind to the west, and negative *v* is wind to the south. So with both negative, that wind is blowing to the south-west, i.e. *from* the north-east. So we expect an angle between 0° (due north) and 90° (due east), and that is just what we see!"},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"def make_rose(df):\n    \"\"\"\n    Convert wind data to wind rose data.\n    Adapted from https://gist.github.com/phobson/41b41bdd157a2bcf6e14.\n    \"\"\"\n    \n    def speed_labels(bins, units):\n        labels = []\n        for left, right in zip(bins[:-1], bins[1:]):\n            if left == bins[0]:\n                labels.append('calm'.format(right))\n            elif np.isinf(right):\n                labels.append('>{} {}'.format(left, units))\n            else:\n                labels.append('{} - {} {}'.format(left, right, units))\n        return labels\n\n    total_count = len(df)\n    calm_count = len(df.loc[df.mag == 0.0])\n    speed_bins = [-1, 0, 1, 2, 3, 4, 5, np.inf]\n    speed_labs = speed_labels(speed_bins, units=\"m/s\")\n    dir_bins = np.arange(-7.5, 370, 15)\n    dir_labels = (dir_bins[:-1] + dir_bins[1:]) / 2\n    \n    rose = (\n        df.assign(\n            speed_bins=lambda df: pd.cut(df[\"mag\"], bins=speed_bins, labels=speed_labs, right=True)\n        ).assign(\n            dir_bins=lambda df: pd.cut(df['compass'], bins=dir_bins, labels=dir_labels, right=False)\n        ).replace({'dir_bins': {360: 0}})\n        .groupby(by=['speed_bins', 'dir_bins'])\n        .size()\n        .unstack(level='speed_bins')\n        .fillna(0)\n        .assign(calm=lambda df: calm_count / df.shape[0])\n        .sort_index(axis=1)\n        .applymap(lambda x: x / total_count * 100)\n    )\n    \n    # Fill empty rows in rose DF where no wind was recorded in that direction\n    # Otherwise plotting complains\n    empty = {k: 0 for k, v in rose.loc[0].to_dict().items()}\n    for d in np.arange(0, 360, 15):\n        if d not in rose.index:\n            s = pd.Series(empty)\n            s.name = d\n            rose = rose.append(s)\n    rose = rose.sort_index()\n    \n    return rose","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"def draw_rose(rose, size=10):\n    \"\"\"\n    Draw a wind rose.\n    Adapted from: https://gist.github.com/phobson/41b41bdd157a2bcf6e14\n    \"\"\"\n    \n    def convert_dir(directions):\n        N = directions.shape[0]\n        barDir = directions * np.pi/180. - np.pi/N\n        barWidth = 2 * np.pi / N\n        return barDir, barWidth\n    \n    palette = sns.color_palette('inferno', n_colors=rose.shape[1])\n    bar_dir, bar_width = convert_dir(np.arange(0, 360, 15))\n\n    fig, ax = plt.subplots(figsize=(size, size), subplot_kw=dict(polar=True))\n    ax.set_theta_direction('clockwise')\n    ax.set_theta_zero_location('N')\n\n    for n, (c1, c2) in enumerate(zip(rose.columns[:-1], rose.columns[1:])):\n        if n == 0:\n            # first column only\n            ax.bar(bar_dir, rose[c1].values, \n                   width=bar_width,\n                   color=palette[0],\n                   edgecolor='none',\n                   label=c1,\n                   linewidth=0)\n\n        # all other columns\n        ax.bar(bar_dir, rose[c2].values, \n               width=bar_width, \n               bottom=rose.cumsum(axis=1)[c1].values,\n               color=palette[n+1],\n               edgecolor='none',\n               label=c2,\n               linewidth=0)\n\n    leg = ax.legend(loc=(0.75, 0.95), ncol=2)\n    xtl = ax.set_xticklabels(['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW'])\n    ax.yaxis.set_major_formatter(ticker.PercentFormatter(decimals=0))\n    ax.tick_params(axis='both', which='both', labelsize=14)\n    \n    return fig, ax","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"To understand the wind data in a more intuitive way, let's create a [wind rose](https://en.wikipedia.org/wiki/Wind_rose). This shows, for each wind direction (remember: where the wind comes *from*!), what percentage of days the wind blew that direction (how far out the petal stretches) and what the wind speed was on those days (the petal's colour)."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"w_mean = gfs_df.mean()[\"mag\"]\nrose = make_rose(gfs_df)\nprint(f\"Mean wind speed is {w_mean:.2f} m/s\")\nfig = draw_rose(rose)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## And what does the wind tell us?\nPuerto Rico is windy! The wind rose shows that the wind in San Juan almost always blows from the east, which confirms our guess that no pollution will be blown into Puerto Rico (assuming the wind is similar all over the island). There wasn't a single day with wind blowing from another direction than easterly, and very few days with less than about 3 m/s of wind (6 knots for the nautically inclined, otherwise 11 km/h).\n\nBased on this, we expect NO<sub>2</sub> plumes to be blown out to the west, and in general we don't expect NO<sub>2</sub> to hang around for very long before being blown off Puerto Rico altogether.\n\n# Investigating Plumes\n## Let's see what Puerto Rico looks like on more and less windy days\nFinally we're going to look at the raw-ish NO<sub>2</sub> satellite data and see if we can intuitively tell what's going on.\n\nThis should tell us something about the plumes of NO<sub>2</sub>. If we define 'calm' as 3 m/s (not that calm!), we can get a different picture for days above and below that cut-off."},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"def s5p_arr_timeseries(s5p_path, gfs_df, max_cf=0.3, disable_tqdm=False):\n    \"\"\"\n    Create a timeseries of S5P arrays showing NO2 column density.\n    Output a list of dicts, including wind magnitude so we can filter with that.\n    \"\"\"\n    \n    s5p_arrs = []\n    for f in tqdm(list(s5p_path.iterdir()), disable=disable_tqdm):\n        date, time = f.stem.split(\"no2_\")[1].split(\"_\")[0].split(\"T\")\n        date = datetime(int(date[:4]), int(date[4:6]), int(date[6:8]), int(time[:2]), int(time[2:4]), int(time[4:]))\n        mean_wind_mag = gfs_df.loc[gfs_df.index.date == date.date()][\"mag\"].mean()\n        with rio.open(f) as rd:\n            cf = np.nanmean(rd.read(7))  # cloud\n            cf = cf <= max_cf\n            arr = rd.read(2)  # tropo\n            aff = rd.transform\n\n            arr = arr * cf  # only include relatively cloud free days\n            arr, aff = flipud(arr, aff)  # fix the upside down data!\n        s5p_arrs.append({\"date\": date, \"wind\": mean_wind_mag, \"arr\": arr, \"aff\": aff})\n    return s5p_arrs, aff","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-output":false,"_kg_hide-input":true},"cell_type":"code","source":"print(\"Loading wind data.\")\ns5p_arrs, s5p_aff = s5p_arr_timeseries(s5p_path, gfs_df)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"wind_cutoff = 3  # this is pretty arbitrary!\ns5p_wind = [w[\"arr\"] for w in s5p_arrs if w[\"wind\"] >= wind_cutoff]\ns5p_calm = [w[\"arr\"] for w in s5p_arrs if w[\"wind\"] < wind_cutoff]\ns5p_wind_mean = np.nanmean(np.stack(s5p_wind), axis=0)\ns5p_calm_mean = np.nanmean(np.stack(s5p_calm), axis=0)\nprint(f\"Number of windy days: {len(s5p_wind)}\")\nprint(f\"Number of calm days: {len(s5p_calm)}\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## And how does it look overlaid on Puerto Rico?\nBefore comparing, let's take a look at the windy mean on a map of Puerto Rico for some context. As expected, big plumes behind San Juan and some of the other power stations, and mostly lower levels elsewhere. Surprisingly little behind the Costa Sur and EcoElectrica stations (south west), which produced the most energy in 2018."},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"def cm2rgb(hexcm):\n    \"\"\"Create an RGB (scale 0-1) version of the provided hex colormap function.\"\"\"\n    def rgbcm(x):\n        h = hexcm(x).lstrip('#')\n        return tuple(int(h[i:i+2], 16)/255 for i in (0, 2, 4))\n    return rgbcm","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"def add_no2_to_map(arr, m):\n    \"\"\"\n    Add a raster image to a Folium map.\n    \"\"\"\n    \n    arr = arr.copy() * 1e6  # becomes mol/km2 instead of mol/m2\n    no2cm = branca.colormap.linear.YlOrRd_09.scale(0, 20)\n    no2cm.caption = 'Tropospheric NO2 density in mol/km2'\n    no2cm.add_to(m)\n\n    folium.raster_layers.ImageOverlay(\n        image=arr,\n        bounds=[[18.6,-67.3,],[17.9,-65.2]],\n        colormap=cm2rgb(no2cm),\n        opacity=0.7,\n    ).add_to(m)\n    return m","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":false},"cell_type":"code","source":"m = map_plants(plants)\nm = add_no2_to_map(s5p_wind_mean, m)\nm","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Now that we know what the different plumes represent, let's compare windy to calm days."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"def compare_no2_fields(arr_wind, arr_calm):\n    \"\"\"Plot comparison of windy to calm days.\"\"\"\n    fig, (ax1, ax2) = plt.subplots(nrows=2, ncols=1, figsize=(20, 8))\n    ax1.imshow(arr_wind, cmap=\"YlOrRd\", vmin=0, vmax=2e-5)\n    ax1.set_title(\"Windy\", fontsize=16)\n    ax2.imshow(arr_calm, cmap=\"YlOrRd\", vmin=0, vmax=2e-5)\n    ax2.set_title(\"Calm\", fontsize=16)\n    sns.despine()\n    ax1.xaxis.grid(False)\n    ax1.yaxis.grid(False)\n    ax2.xaxis.grid(False)\n    ax2.yaxis.grid(False)\n    ax1.axis(\"off\")\n    ax2.axis(\"off\")\n    plt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"compare_no2_fields(s5p_wind_mean, s5p_calm_mean)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Pollution hangs around when it's less windy!\nUnsurprisingly...The cutoff of 3 m/s is pretty high, so even on the 'calm' days there is quite a plume to the west. Unfortunately there weren't enough real calm days to do otherwise! As will become clear below, this is quite an important part of our analysis. Running this analysis in different areas will require careful consideration of wind distributions. First step: make a wind rose!\n\n## Does this tell us anything about emissions?\n**Yes!**\n\n# Using NO<sub>2</sub> Line Density to Determine Emissions\nWorking from the [paper](https://scihub.bban.top/10.1126/science.1207824) *Megacity Emissions and Lifetimes of Nitrogen Oxides Probed from Space* by Beirle et al. (published in Science), I can use the characteristics of the plume on windy days to determine the emissions of NO<sub>x</sub> in kg/hr! Two useful additional sources: [supporting material](https://science.sciencemag.org/content/sci/suppl/2011/09/21/333.6050.1737.DC1/Beirle.SOM.pdf) and a [follow-up paper](https://www.atmos-chem-phys.net/16/5283/2016/acp-16-5283-2016.pdf).\n\nThey propose to integrate the line-density along a line extending up- and down-wind of the point of interest, taking the average mol/m over a line stretching perpendicular to the wind direction. This can then be fit with the following model function\n\n![2020-03-20_16-40-35.png](attachment:2020-03-20_16-40-35.png)\n\nwhere\n- *M* is the overall fit\n- *E* is the emissions scaling factor in mol/s - **this is what we want!**\n- *x* is the location of the measurement\n- *X* is the virtual source location (may be up- or down-wind from the actual source)\n- *x<sub>0</sub>* is the folding distance, which controls how quickly the plume decays\n- *G* is a Gaussian smoothing function and *σ* is it's standard deviation\n\n## So if we can measure this line density and fit the function, we get the emission rate!\nLet's get to it.\n\nI'll go 50 km up- and down-wind, and at each point get the line density for a swath 40 km wide.\nIdeally I should convert to a projected [CRS](https://docs.qgis.org/3.4/en/docs/gentle_gis_introduction/coordinate_reference_systems.html) or use the [Haversine formula](https://en.wikipedia.org/wiki/Haversine_formula) but for now let's just rely on the fact that, near the equator 1 km ~= 0.01 degrees. This will make my life a bit easier, and I'm anyway only sampling at the resolution of the provided raster data, so won't be losing much (or any) accuracy.\n\nFrom inspection of the wind rose from earlier, the median wind direction is directly westerly. Let's quickly check this:","attachments":{"2020-03-20_16-40-35.png":{"image/png":"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"}}},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"median_wind_direction = gfs_df.loc[gfs_df.mag > 3].compass.median()\nprint(f\"Median wind direction: {median_wind_direction:.0f}°\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"So this is actually slightly south of exactly west, but let's stick to a east-west cross-section for our analysis. I suspect if I took into account the higher impact of windier days (which may have a stronger than linear impact) I'd end up concluding the median wind *effect* is due west. Why? Because the NO<sub>2</sub> plume is going straight east! Note: I could also rotate the NO<sub>2</sub> fields on each day to line up with the wind direction for that day, but as the wind is already basically directly east-west, this probably isn't necessary. In a location with more varied wind directions, one could either calculate a separate profile for each direction, or do this NO<sub>2</sub> field rotation.\n\nI want to quickly codify this in our list of power plants. However, to avoid double work, I'm going to take another step in simplifying our plants list by merging those that are right next to each other, as they'll be indistinguishable in terms of satellite imagery.\n\nThe table now has five entries, and those where multiple plants were close together have both names listed as well as both fuel types. I've also entered the primary wind direction for our first investigation."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"def merge_close_plants(plants, dist_deg=0.05):\n    \"\"\"\n    Merge plants that are close together.\n    Sum numerical values, list others.\n    \"\"\"\n    \n    buff = plants.copy()\n    buff.geometry = buff.geometry.buffer(dist_deg)\n    buff = gpd.GeoDataFrame(geometry=[p for p in buff.unary_union])\n    buff[\"centroid\"] = buff.geometry.centroid\n\n    plants = gpd.sjoin(plants, buff, op=\"within\")\n\n    grouped = plants.groupby(\"index_right\").sum()\n    grouped[\"geometry\"] = plants.groupby([\"index_right\"])[\"centroid\"].first()\n    grouped[\"primary_fuel\"] = plants.groupby([\"index_right\"])[\"primary_fuel\"].apply(lambda x: \", \".join(x))\n    grouped[\"name\"] = plants.reset_index().groupby([\"index_right\"])[\"index\"].apply(lambda x: \", \".join(x))\n    grouped = grouped.set_index(\"name\")\n    grouped[\"capacity_factor\"] = grouped[\"generation_hourly\"] / grouped[\"capacity_mw\"]\n    \n    grouped = gpd.GeoDataFrame(grouped)\n    grouped[\"xy\"] = grouped.geometry.apply(lambda geom: [float(g[0]) for g in geom.coords.xy])\n    \n    return grouped","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"plants = merge_close_plants(plants)\nplants = pretty_cols(plants)\nplants[\"wind_dir\"] = \"\"\nplants.at[\"San Juan CC, Palo Seco\", \"wind_dir\"] = 90\nplants[[\"Primary Fuel\", \"Capacity (MW)\", \"Hourly Generation (MWh)\", \"Capacity Factor\", \"wind_dir\"]]","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Now we're finally ready to start our in-depth investigation!"},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":false},"cell_type":"code","source":"def coords2pixel(aff, x, y):\n    \"\"\"\n    Convert x, y in degrees to pixel coordinates in the array.\n    Returned in row, col format as expected by numpy.\n    Useful reference: https://www.perrygeo.com/python-affine-transforms.html\n    \"\"\"\n    \n    col, row = ~aff * (x, y)\n    col, row = int(col), int(row)\n    return row, col","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":false},"cell_type":"code","source":"def get_line_density(arr, aff, x, y, length=100, width=20, angle=0):\n    \"\"\"\n    Calculate line density plot for given image and x, y coordinates.\n    Length and width are in km, angle is mathematical angle.\n    (i.e. N=pi/2, E=0, S=-pi/2, W=pi=-pi)\n    \"\"\"\n    \n    arr_lines = np.zeros_like(arr)\n    row1, col1 = coords2pixel(aff, x, y)\n    samples = []\n    up = int(length*1e3 / 2)\n    for x_move in range(-up, up + 1, 100):\n        line = []\n        side = int(width*1e3 / 2)\n        for y_move in range(-side, side + 1, 100):\n            x_new = x + (x_move * cos(angle) - y_move * sin(angle)) * 1e-5\n            y_new = y + (y_move * cos(angle) + x_move * sin(angle)) * 1e-5\n            row, col = coords2pixel(aff, x_new, y_new)\n            if row < 0 or col < 0:\n                continue\n            try:\n                line.append(arr[row, col])\n                arr_lines[row, col] = 1\n                arr_lines[row, col] = 1\n            except IndexError:\n                pass\n        if len(line) > 0:\n            line_mean = np.mean(line) * width * 1e3  # to go from mol/m2 to mol/m\n            samples.append({\"x\": x_move, \"val\": line_mean})\n    line_df = pd.DataFrame(samples)\n    line_df = line_df.set_index(\"x\")\n    return line_df, arr_lines","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-output":false,"_kg_hide-input":true},"cell_type":"code","source":"x, y = plants.at[\"San Juan CC, Palo Seco\", \"xy\"]\nline_df, sample_loc = get_line_density(s5p_wind_mean, s5p_aff, x, y, length=160, width=40, angle=0)\nxx = np.array(line_df.index.to_list())","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"The plot below shows the extents of sampling, up- and down-wind and to either side."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"def draw_sample_area(arr_no2, arr_sample):\n    \"\"\"Plot NO2 data with sample area overlaid.\"\"\"\n    fig, ax = plt.subplots(figsize=(12, 6))\n    ax.imshow(arr_no2, cmap=\"YlOrRd\", vmin=0, vmax=2e-5)\n    ax.imshow(arr_sample, cmap=ListedColormap([(0, 0, 0, 0), (0, 0, 0, .2)]))\n    ax.grid(False)\n\n    ax.set_title(\"NO2 with sample area\", fontsize=16)\n    sns.despine()\n    ax.xaxis.grid(False)\n    ax.yaxis.grid(False)\n    ax.axis(\"off\")\n    plt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"draw_sample_area(s5p_wind_mean, sample_loc)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## And this is what the profile looks like!"},{"metadata":{"_kg_hide-output":true,"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"def plot_density(measured, fit_xx=None, fit_yy=None, size=12):\n    \"\"\"Plot NO2 density versus profile position.\"\"\"\n    \n    sns.set_style(\"whitegrid\")\n    fig, ax = plt.subplots(figsize=(size, size/2))\n    x = measured.index\n    spl = UnivariateSpline(x, measured.val)\n    spl.set_smoothing_factor(0.005)\n    ax.plot(x, spl(x), label=\"Measured\", linewidth=4)\n    ax.set_ylabel(\"NO2 line density (mol/m)\", fontsize=16)\n    ax.set_xlabel(\"x (km)\", fontsize=16)\n    ax.set_title(\"Line density profile (positive x is upwind)\", fontsize=16)\n    ax.set_xlim([-80e3 ,80e3])\n    ticks = ticker.FuncFormatter(lambda x, pos: '{0:g}'.format(x*1e-3))\n    ax.xaxis.set_major_formatter(ticks)\n    ax.set_ylim([0, 0.5])\n    ax.arrow(20e3, 0.1, -40e3, 0, head_width=0.03, head_length=5e3, fc='k', ec='k')\n    ax.text(-18e3, 0.12, \"wind direction\", fontsize=20)\n    \n    if fit_xx is not None:\n        ax.plot(fit_xx, fit_yy, c=\"red\", linewidth=4)\n        ax.legend([\"Measured\", \"Fit\"])\n        \n    ax.xaxis.grid(False)\n    ax.tick_params(axis='both', which='both', labelsize=12)\n    sns.despine(left=True)\n    plt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"plot_density(line_df)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## So let's set up the model function\nThese are the functions *e*, *G* and *M* explained before."},{"metadata":{"trusted":true},"cell_type":"code","source":"e = np.vectorize(lambda x, x0, X: np.exp((x+X)/x0) if x <= -X else 0)\nG = lambda x, std: np.exp(-(x**2)/(2*(std**2)))/(np.sqrt(2*np.pi)*std)\nM = lambda x, E, B, X, x0, std: E * np.convolve(e(x, x0, X), G(x, std), mode=\"same\") + B","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Now we use a curve fit to find the parameters\nThis will give the values of *E*, *B*, *X*, *x<sub>0</sub>*, and *σ* that make the model best fit the data."},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":false},"cell_type":"code","source":"def fit_model(M, xx, yy):\n    p, cov = curve_fit(\n        f=M,\n        xdata=xx,\n        ydata=yy,\n        p0=(30, 0.25, 1e3, 5e4, 1e4),\n        bounds=(\n            (0, 0, -np.inf, -np.inf, -np.inf),\n            (np.inf, np.inf, np.inf, np.inf, np.inf)\n        ),\n        method=\"trf\",\n    )\n    return p","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-output":false,"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"E, B, X, x0, std = fit_model(M, xx, line_df.val.to_list())","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"print(\n    f\"And these are our values!\\nE:\\t{E:.2f} mol/s\"\n    f\"\\nB:\\t{B:.3f} mol/m\"\n    f\"\\nX:\\t{X:.0f} m\"\n    f\"\\nx0:\\t{x0:.0f} m\"\n    f\"\\nstd:\\t{std:.0f}\"\n)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Let's plot this model fit against the measured data and see how it looks."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"yy = M(x=xx, E=E, B=B, X=X, x0=x0, std=std)\nplot_density(line_df, xx, yy)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## It looks pretty good!\nNow let's convert this *E* value to something more intelligible than mol/s and check some other things.\n\nFirstly, the NO<sub>2</sub> decay time, *τ*, which is by dividing the *x<sub>0</sub>* distance by the mean wind speed."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"sec_per_hour = 3600\nno2_mol_mass = 46e-3  # kg/mol\n\ntau = (x0 / w_mean) / sec_per_hour  # to convert to hours\nprint(f\"NO2 decay time: {tau:.2f} hours\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"which agrees pretty well with the literature range of 3 - 8 hours.\n\nThen we convert *E* in mol/s to output in kg/hr"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"Ekghr = E * sec_per_hour * no2_mol_mass\nprint(f\"NO2 output is {Ekghr:.0f} kg/hr\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"The total hourly electricity generation of the San Juan area is 416 MWh/h. We can use this with the emissions above to calculate the emissions factor in kg/MWh!"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"emission_fac = Ekghr / plants.loc[\"San Juan CC, Palo Seco\", \"generation_hourly\"]\nprint(f\"Naive emissions factor: {emission_fac:.2f} kg/MWh\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Is that it!??\nThis is quite above the expected range of 2 - 3 kg/MWh, but I've forgotten one thing...\n\n## Cars!!\nIf I can find a way to account for cars (and other sources of NO<sub>2</sub>), I should be able to calculate the real value! According to [this random source](http://www.apis.ac.uk/overview/pollutants/overview_nox.htm) from the UK, road traffic can account for more than 30% of emissions.\n\nBut how do we calculate which portion comes from cars and which from other sources?\n\n# Modelling All the Emissions Sites\n## The plan\nI'll select a bunch of sites and then perform the curve-fitting procedure on each one. Except this time, I'll add an extra factor to account for other sources of emission. I'm also going to include a site with *no* power generation, so help solve for the unknown. I'll take total [night time lights](https://www.nasa.gov/topics/earth/earthday/gall_earth_night.html) emissions in a radius around each point and take that as an indicator of non-power-plant NO<sub>2</sub> emissions. Then I can disaggregate our *E* factor into two terms:\n\n*E = E_elec + f_other x NTL*\n\nwhere\n- *E* is the total emissions\n- *E_elec* is the emissions just from the power plants (what we're after!)\n- *f_other* is some multiplier of emissions per light-output that we need to discover\n- *NTL* is the known total light emissions for the area"},{"metadata":{},"cell_type":"markdown","source":"## Let's choose our points and see what happens!\nI selected the following points. These are the main fossil-fuel power plants, plus a large city near San Juan called Caguas. For this city, we know that *E_elec* will be exactly zero.\nUsing these locations, along with some knowledge of the fuels and generation activity of each plant, we have enough data points to solve the different unknown *E_elec* values along with the constant *f_other* factor that accounts for non-power NO<sub>2</sub> emissions.\n\nI'm also going to exclude the Cambalache and Mayagüez plants: they are both very small producers (less than 1.5% of our selected sites) and the first is directly under San Juan's plume and won't be detectable, and the second is hovering on the edge of our satellite image, so we won't see its full plume. (It would be trivial to get more data to deal with this, but we're trying to keep things as simple as possible for now!)\n\nSo let's see the table with the latest chops and changes."},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"plants = plants.loc[plants.generation_hourly > 100]\nplants.loc[\"Caguas\"] = {c: 0 for c in plants.columns}\nplants.at[\"Caguas\", \"xy\"] = [-66.04, 18.24]\nplants.at[\"Caguas\", \"geometry\"] = Point([-66.04, 18.24])\nplants.at[\"Caguas\", \"Primary Fuel\"] = plants.at[\"Caguas\", \"primary_fuel\"] = \"none\"\nplants.at[\"Caguas\", \"wind_dir\"] = \"\"\nplants[[\"Primary Fuel\", \"Capacity (MW)\", \"Hourly Generation (MWh)\", \"Capacity Factor\", \"wind_dir\"]]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"print(f\"Still our included plants account for {(100 * plants.capacity_mw.sum() / gppd.capacity_mw.sum()):.0f}% of the total system capacity.\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Great! So let's stick a 5 km buffer around each of our chosen sites and see what it looks like (in red below). Note also the new green marker at the town of Caguas."},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"plants_buff = plants.copy()\nplants_buff.geometry = plants_buff.geometry.buffer(0.05)\n\nm = map_plants(plants)\nfolium.features.GeoJson(\n    plants_buff.to_json(),\n    style_function=lambda x: {\n        \"fillOpacity\": 0.5,\n        \"weight\": 0,\n        \"fillColor\": \"red\",\n    },\n).add_to(m)\nm","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Now we get the total night lights in each circle\nFirst our night time lights. Seems to make sense - hot spots around San Juan and other towns."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"with rio.open(viirs_path) as rd:\n    viirs = rd.read(1)\n    viirs[np.isnan(viirs)] = 0\n    viirs_aff = rd.transform\nfig, ax = plt.subplots(figsize=(20, 10))\nax.imshow(viirs, vmin=0, vmax=30)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Now we see the total light emitted for each area:"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"ntl_sums = zonal_stats(\n    plants_buff,\n    viirs,\n    affine=viirs_aff,\n    stats=[\"sum\"],\n    nodata=-999,\n)\nplants[\"NTL\"] = [int(n[\"sum\"]) for n in ntl_sums]\nplants[[\"NTL\"]]","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"As expected, high NTL from San Juan and Caguas, but much less from the other plant sites that aren't in major towns."},{"metadata":{},"cell_type":"markdown","source":"## Now let's check the wind roses for each site"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"for idx, row in plants.iterrows():\n    print(\"Site:\", idx)\n    x, y = row[\"xy\"]\n    gfs_df = get_wind_data_point(gfs_path, x=x, y=y, disable_tqdm=True)\n    rose = make_rose(gfs_df)\n    fig, ax = draw_rose(rose, size=5)\n    plt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"They're all pretty similar, and all just about majority easterly wind, although the sites on the south coast do have a bit of a northerly component, and a bit weaker pverall. for simplicity let's just do all our wind profiles east-west. And let's set this decision into our table (remember 90° is east!)."},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"plants[\"wind_dir\"] = 90\nplants[[\"wind_dir\"]]","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Now the curve fit procedure for each site\nWhich gives the emissions (*E*) for each site in kg/hr."},{"metadata":{"_kg_hide-input":true,"_kg_hide-output":false,"trusted":true},"cell_type":"code","source":"def get_E_value(x, y, sample_length=160):\n    gfs_df = get_wind_data_point(gfs_path, x=x, y=y, disable_tqdm=True)\n    \n    s5p_arrs, aff = s5p_arr_timeseries(s5p_path, gfs_df, disable_tqdm=True)\n    wind_cutoff = 2.5\n    s5p_wind = [w[\"arr\"] for w in s5p_arrs if w[\"wind\"] >= wind_cutoff]\n    s5p_wind_mean = np.nanmean(np.stack(s5p_wind), axis=0)\n    \n    line_df, sample_loc = get_line_density(s5p_wind_mean, s5p_aff, x, y, length=sample_length, width=40)\n    xx = np.array(line_df.index.to_list())\n    E, B, X, x0, std = fit_model(M, xx, line_df.val.to_list())\n    yy = M(x=xx, E=E, B=B, X=X, x0=x0, std=std)\n    plot_density(line_df, xx, yy, size=6)\n    \n    return E","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":false},"cell_type":"code","source":"plants[\"E_kghr\"] = 0\nfor idx, row in plants.iterrows():\n    print(\"Site:\", idx)\n    x, y = row[\"xy\"]\n    E = get_E_value(x, y)\n    plants.at[idx, \"E_kghr\"] = E * sec_per_hour * no2_mol_mass","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Clearly there is something wrong with the Costa Sur/EcoElectrica site! The line density profile makes no sense and there's no clear jump in NO2 behind the power plants. Either it is emitting very little NO2, or it just isn't showing up for some reason. Unfortunately I'll have to exclude it from our analysis for now.\n\nLet's have a look at the values for E in kg/hr for the remaining sites (remember this is the *total* E at each point, for electricity and other sources):"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"plants = plants.drop([\"Costa Sur, EcoEléctrica\"])\nplants[[\"E_kghr\"]]","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Now we can solve for the unknown values\nWe have\n\n*E = E_elec + f_other x NTL*\n\nfor each of the points. And we know Caguas has *E_elec = 0* which let's us solve for *f_other*:\n\n*f_other = (E - E_elec)/NTL = E/NTL*"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"f_other = float(plants.at[\"Caguas\", \"E_kghr\"] / plants.at[\"Caguas\", \"NTL\"])\nprint(f\"f_other = {f_other:.4f}\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Now we can use *f_other* and *NTL* to calculate *Eelec* for each of the other stations"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"plants[\"E_elec_kghr\"] = (plants[\"E_kghr\"] - f_other * plants[\"NTL\"]).astype(int)\nplants[[\"Capacity (MW)\", \"Hourly Generation (MWh)\", \"NTL\", \"E_kghr\", \"E_elec_kghr\"]]","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"And then we just use the estimated generation for each plant to calculate its emissions factor and then we can get a weighted emissions factor!"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"plants[\"EF\"] = round(plants[\"E_elec_kghr\"] / plants[\"generation_hourly\"], 2)\nplants.at[\"Caguas\", \"EF\"] = 0\nplants[[\"Primary Fuel\", \"Capacity (MW)\", \"Hourly Generation (MWh)\", \"EF\"]]","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"EF_mean = np.average(plants[\"EF\"], weights=plants[\"generation_hourly\"])\nprint(f\"Weighted average EF is {EF_mean:.2f} kg/MWh\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Is that it?? Are we done yet?\nJust about. We must bring back in the excluded power plants, some of which have emissions that we couldn't measure, and others which have no emissions."},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"eia = pd.read_csv(eia_path)\neia[\"generation_hourly\"] = eia[\"Netgen_Total\"] / (265 * 24)\ntotal_gen = eia.generation_hourly.sum()\nfossil_codes = [\"NG\", \"BIT\", \"DFO\", \"RFO\"]\ntotal_fossil_gen = eia.loc[eia.Fuel_Code.isin(fossil_codes)].generation_hourly.sum()\ncaptured_gen = plants.generation_hourly.sum()","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"print(f\"Total average hourly generation for Puerto Rico:\\t{total_gen:.0f} MWh\")\nprint(f\"Of which, the amount that is polluting:\\t\\t\\t{total_fossil_gen:.0f} MWh\\t({(100*total_fossil_gen/total_gen):.0f}% of total)\")\nprint(f\"Of which, the amount included in the analysis:\\t\\t{captured_gen:.0f} MWh\\t\\t({(100*captured_gen/total_gen):.0f}% of total)\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"There's no single way to deal with this, but we can do our best. We can either:\n1. Assume our EF above applies to all energy produced on the island;\n2. Assume the rest have no emissions;\n3. Assume they follow general trends of pollution for their fuel type (coal, oil, or gas).\n\nNumber 1 is the number we already calculated, which is probably an overestimate, while number 2 will be significantly lower and is probably an under-estimate. Let's quickly try method 3 and see what it gives us.\n\nAll the emissions from the two San Juan plants are oil. Therefore we can take that site's EF as the Puerto Rico EF for oil. The other site we measured was a mix of coal and oil. Using the oil EF we calculate, we can use the weighted generation (by oil and by coal) to calculate the EF for coal. We have no information about gas... But luckily it doesn't account for much of Puerto Rico's generation. Going by our generic EF values from way up top, let's say it's five times lower than EF for oil. Thus we have for all three:"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"EF_oil = plants.at[\"San Juan CC, Palo Seco\", \"EF\"]\nprint(f\"EF for oil: {EF_oil:.2f} kg/MWh\")\n\ngen_coal = int(eia.loc[eia.Plant_Name == \"AES Puerto Rico\", \"generation_hourly\"])\ngen_oil = int(eia.groupby(\"Plant_Name\").sum().at[\"Aguirre Plant\", \"generation_hourly\"])\nEF_coal = (((plants.at[\"A.E.S. Corp., Aguirre\", \"EF\"]) * (gen_coal + gen_oil)) - (gen_oil * EF_oil))/gen_coal\nprint(f\"EF for coal: {EF_coal:.2f} kg/MWh\")\n\nEF_gas = EF_oil / 5\nprint(f\"EF for gas: {EF_gas:.2f} kg/MWh\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Now we're ready to extrapolate our total emissions."},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"def calc_emissions(row):\n    if row.Fuel_Code in [\"NG\"]:\n        return EF_gas * row[\"generation_hourly\"]\n    elif row.Fuel_Code in [\"BIT\"]:\n        return EF_coal * row[\"generation_hourly\"]\n    elif row.Fuel_Code in [\"DFO\", \"RFO\"]:\n        return EF_oil * row[\"generation_hourly\"]\n    else:\n        return 0\neia[\"emissions_kghr\"] = eia.apply(calc_emissions, axis=\"columns\")","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"EF_method3 = eia.emissions_kghr.sum() / eia.generation_hourly.sum()\nprint(f\"EF if we extrapolate to other plants: {EF_method3:.2f} kg/MWh\")\nprint(\"This is probably the best estimate!\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## So there we have it, final answer!\nOr a bit lower if we assume the other plants don't pollute, or a bit higher if we assume uniform emissions.\n\n# Bonus Points\n## We can now calculate the marginal emissions factor\nFor a marginal emissions factor to be meaningful, we'd need a lot more generation data, down to daily or better resolution. This would enable an understanding of, at each point in time, what the environmental impact of a decision to use electricity would be. Even an average factor would need to take into account which plants are historically at the margin of additional power demand. Since we don't have that, let's just assume that marginal power comes from fossil fuel plants. There are quite a few ways we could go about this in the absence of data...\n\nLet's say that coal is unlikely to be on the margin (oil and gas are hapipier being ramped up and down), so our marginal power will come from either gas or oil. Then we can calculate aan average (weighted by generation) of the emission factors of each of these fuels."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"gen_gas = eia.loc[eia.Fuel_Code == \"NG\"].Netgen_Total.sum()\ngen_oil = eia.loc[eia.Fuel_Code.isin([\"RFO\", \"DFO\"])].Netgen_Total.sum()\nEF_marginal = (EF_gas * gen_gas + EF_oil * gen_oil)/(gen_gas + gen_oil)\nprint(f\"Marginal EF: {EF_marginal:.2f} kg/MWh\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## A bit lower, but just because we left out coal\nThis really requires a bit more knowledge about Puerto Rico's daily generation and price-demand curves!\n\n## What about calculating the EF on a monthly basis?\nIn theory easy (just apply the method to only a month of data at a time), but as there's only one NO2 image per day, we start to stretch it a bit thin. Will do it and we can see what the results look like and if they correlate with anything. No expectation that they will change dramatically, as renewable (wind, solar, hydro) penetration isn't high enough (~6%) to drastically change the proportion of electricity coming from fossil (polluting) sources.\n\nLet's see how we would do this just for San Juan. First we create a new dict with separate NO2 and wind data for each month."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"def calc_monthly(x, y):\n    gfs_df = get_wind_data_point(gfs_path, x=x, y=y, disable_tqdm=True)\n    s5p_arrs, aff = s5p_arr_timeseries(s5p_path, gfs_df, disable_tqdm=True)\n    \n    s5p_arrs = sorted(s5p_arrs, key=lambda x: x[\"date\"])\n    s5p_monthly = {}\n    for d in s5p_arrs:\n        year = d[\"date\"].year\n        month = d[\"date\"].month\n        ym =  f\"{year}-{month:02}\"\n        if ym not in s5p_monthly.keys():\n            s5p_monthly[ym] = []\n        s5p_monthly[ym].append(d)\n    \n    results = {}\n    for month, vals in s5p_monthly.items():\n        wind_cutoff = 2.5\n        s5p_wind = [w[\"arr\"] for w in vals if w[\"wind\"] >= wind_cutoff]\n        s5p_wind_mean = np.nanmean(np.stack(s5p_wind), axis=0)\n        line_df, sample_loc = get_line_density(s5p_wind_mean, aff, x, y, length=160, width=40)\n        xx = np.array(line_df.index.to_list())\n        try:\n            E, B, X, x0, std = fit_model(M, xx, line_df.val.to_list())\n        except RuntimeError:\n            E = np.nan\n        results[month] = int(E * sec_per_hour * no2_mol_mass)\n    return results","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"x, y = plants.at[\"San Juan CC, Palo Seco\", \"xy\"]\nE_monthly = calc_monthly(x, y)\nprint(\"Monthly emissions for San Juan area (from all sources!)\")\nmonthly = pd.DataFrame(E_monthly.values(), index=E_monthly.keys(), columns=[\"E_kghr\"])\nmonthly.index = pd.to_datetime(monthly.index)\nmonthly.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"At this point we could repeat the above procedure, using NTL each month (NTL data is available as monthly composites!) to disaggregate power and non-power emissions, and using the monthly generation from each plant to determine a monthly EF factor. However, I think this is sufficient as a demonstration of the potential of this method, Anyway I think we might start stretching our assumptions a bit thin to keep going without stopping for a validity check on how we've done so far!\n\nTo get a quick answer out of this, let's just divide the emissions each month by Puerto Rico's total generation for that month (remembering that the generation is from 2018, whereas our NO2 data is from 2018-2019, but we assume each month has similar characteristics regardless of year). Then we'll just normalise the results to have a mean equal to the annual EF we already calculated!"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"monthly[\"EF_high\"] = np.nan\nfor idx, row in monthly.iterrows():\n    month = idx.strftime(\"%B\")\n    monthly.at[idx, \"EF_high\"] = round(row[\"E_kghr\"] / (eia[f\"Netgen_{month}\"].sum() / (24 * 365)), 2)\nmonthly[\"EF\"] = (monthly[\"EF_high\"] * EF_method3 / monthly[\"EF_high\"].mean()).round(2)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"And this is what it looks like!"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"def plot_monthly_EF(df):\n    sns.set_style(\"whitegrid\")\n    fig, ax = plt.subplots(figsize=(16,6))\n    df[[\"EF\"]].plot(ax=ax, linewidth=4)\n    ax.set_ylim([0, 3])\n    ax.set_title(\"Emissions Factor (EF) by date\", fontsize=16)\n    ax.set_ylabel(\"EF (kg/MWh)\", fontsize=16)\n    ax.set_xlabel(\"Date\", fontsize=16)\n    ax.tick_params(axis='both', which='both', labelsize=12)\n    \n    ax.xaxis.grid(False)\n    ax.legend()\n    sns.despine(left=True)\n    plt.show()","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"plot_monthly_EF(monthly)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## And now we're seriously done!\n\n# Caveats and Recommendations\nAs mentioned, I decided to keep it as simple as possible for this demonstration. There is also quite a long chain of assumptions that could build up to several inaccuracies.\n\nThe most obvious improvement would be to include a larger patch of wind and NO<sub>2</sub> data, extending much further to each side of Puerto Rico (especially downwind!). This should give much more freedom in creating our wind profiles. As mentioned, we should also use atmospheric data that matches month for month with our generation data, but this isn't always possible if we want the latest numbers! (I haven't managed to find 2019 generation numbers for Puerto Rico, but they probably exist at the end of the right email address).\n\nOne big assumption was assuming non-power pollution scales linearly with night time lights (NTL). We should also investigate other data sources such as population distribution, road networks etc, and explore the possibility of a nonlinear relationship (e.g. more night time lights in one area indicates more intensive industry, which could create a much more than linear increase in NO<sub>2</sub> emissions). Our choice of NTL sampling area (circle with 5 km radius) was also essentially random and needs to be investigated.\n\nWe were lucky that Puerto Rico's wind is so well behaved. A more general analysis would require rotating the NO<sub>2</sub> fields (as has been done in the literature) or separatingly sampling along major wind directions (which I wrote the code for above but didn't use). The line density profile also assumes a near point source of emissions, but we know that isn't the case. We might need to investigate including multiple model functions to fit one density line, each representing a different emission source (this might allow us to model the plants further west under San Juan's plume).\n\nWe should also look into additional effects on NO<sub>2</sub>, such as temperature, humidity, precipitation etc (in this analysis I haven't even touched the [GLDAS](https://developers.google.com/earth-engine/datasets/catalog/NASA_GLDAS_V021_NOAH_G025_T3H) dataset). However, without decent training data it will be difficult to include these other data sources that aren't as easy to analyse with a simple, logical approach.\n\nTo apply this method in other areas, there are a few things which would need to be considered (in addition to those mentioned above). The assumption about no wind blowing in would generally not be true, though this shouldn't affect the line density procedure much unless the plumes are very close. For areas with more variable wind, the analysis would either have to be done separately for different wind directions, or the NO<sub>2</sub> fields rotated according to each day's wind. The process of disaggregating power and non-power pollution would need to be assessed, and in dense areas it is likely that many plants would be under the plumes of others, in which case the suggestion above of multiple model functions should be explored."},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat":4,"nbformat_minor":4}