Exerciseschevron_rightChapter 4chevron_right4.5
fitness_center

Exercise 4.5

Multi-Well Comparison Dashboard

Level 2
Chapter 4: NumPy & Pandas
descriptionProblem

The starter builds a long-format DataFrame df, one row per well per month, with oil_bopd, water_bwpd, gas_mscfd, and fwhp_psi for four wells over twelve months. Long format is how production data actually lives in a database, and it's what groupby and per-well plotting expect.

Two parts:

  1. add_derived(df): return a copy of df with two new columns:
  • water_cut = water_bwpd / (oil_bopd + water_bwpd)
  • gor = gas_mscfd × 1000 / oil_bopd (scf/bbl)
  1. Build a 2×2 matplotlib figure (plt.subplots(2, 2, ...)) with one

line per well in each panel: oil rate, water cut, GOR, and flowing wellhead pressure over time. Loop the wells, filter df to each, and plot. Label the axes and add a legend.

Work off the derived frame for the water-cut and GOR panels. Keeping the derivation in one function (rather than recomputing inline per panel) is the habit that keeps a dashboard honest.

lightbulbHints (0/3)

Stuck? Reveal hints one at a time — they progress from nudge to near-solution.

codeYour solution
main.py
visibilityReveal reference solutionexpand_more

Try solving it yourself first — the hints walk you through it. The solution below is one valid approach; yours may differ and still be correct.

import pandas as pd
import matplotlib.pyplot as plt

_specs = [
    ("OD-001", 1800, 0.020, 120, 20,  900, 2200, 10),
    ("OD-002",  900, 0.040, 300, 90,  600, 2050, 14),
    ("OD-005", 2400, 0.015,  80, 15, 1500, 2400,  8),
    ("OD-007", 1300, 0.030, 200, 40,  750, 2150, 12),
]
_rows = []
for _name, _oil0, _doil, _w0, _dw, _gas0, _fwhp0, _dfwhp in _specs:
    for i in range(12):
        _rows.append({
            "well": _name,
            "month": i + 1,
            "oil_bopd": round(_oil0 * (1 - _doil) ** i),
            "water_bwpd": _w0 + _dw * i,
            "gas_mscfd": round(_gas0 * (1 - 0.01) ** i),
            "fwhp_psi": _fwhp0 - _dfwhp * i,
        })
df = pd.DataFrame(_rows)


def add_derived(df):
    out = df.copy()
    out["water_cut"] = out["water_bwpd"] / (out["oil_bopd"] + out["water_bwpd"])
    out["gor"] = out["gas_mscfd"] * 1000 / out["oil_bopd"]
    return out


derived = add_derived(df)

fig, axes = plt.subplots(2, 2, figsize=(12, 8))
# One consistent colour per well across every panel.
colors = {"OD-001": "#2E8B57", "OD-002": "#CC4444", "OD-005": "#4682B4", "OD-007": "#DAA520"}
for well, g in derived.groupby("well"):
    c = colors[well]
    axes[0, 0].plot(g["month"], g["oil_bopd"], color=c, marker="o", label=well)
    axes[0, 1].plot(g["month"], g["water_cut"], color=c, marker="o", label=well)
    axes[1, 0].plot(g["month"], g["gor"], color=c, marker="o", label=well)
    axes[1, 1].plot(g["month"], g["fwhp_psi"], color=c, marker="o", label=well)

axes[0, 0].set_title("Oil rate"); axes[0, 0].set_ylabel("bopd")
axes[0, 1].set_title("Water cut"); axes[0, 1].set_ylabel("fraction")
axes[1, 0].set_title("GOR"); axes[1, 0].set_ylabel("scf/bbl")
axes[1, 1].set_title("Flowing WHP"); axes[1, 1].set_ylabel("psi")
for ax in axes.flat:
    ax.set_xlabel("Month")
    ax.grid(True, alpha=0.3)
axes[0, 0].legend()
fig.suptitle("OML 58 - multi-well comparison", fontweight="bold")
plt.tight_layout()
plt.show()

lockCopying code is a Full Access feature.