Exercise 4.5
Multi-Well Comparison Dashboard
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:
add_derived(df): return a copy ofdfwith two new columns:
water_cut=water_bwpd / (oil_bopd + water_bwpd)gor=gas_mscfd × 1000 / oil_bopd(scf/bbl)
- Build a
2×2matplotlib 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.
Stuck? Reveal hints one at a time — they progress from nudge to near-solution.
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.