Exercise 5.10
Field Report Multi-Panel Capstone
Build a single-page field report, the kind a production manager prints out at the end of every month and sticks on the wall, combining four of the chart types from this chapter into one figure.
The starter provides a 36-month history for 8 wells (hist) plus a snapshot DataFrame (snapshot) of latest rates and water cuts.
Build a 2×2 figure with:
axes[0, 0]: field oil trend. Sum oil rate by month across all
wells; plot total field rate over time.
axes[0, 1]: top 5 wells by current rate as a horizontal bar
chart. Sort snapshot descending and take head(5). Invert the y-axis.
axes[1, 0]: water-cut distribution across the field as a
ax.hist of snapshot["water_cut"].
axes[1, 1]: bubble map of the field: `ax.scatter(lon, lat,
s=oil_bopd/4, c=water_cut, cmap="RdYlGn_r")`.
Add a fig.suptitle("..."), axis labels on each panel, and a colorbar on the bubble map.
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 numpy as np
import pandas as pd
import matplotlib.pyplot as plt
np.random.seed(17)
months = np.arange(0, 36)
wells = [
("OD-001", 1500, 0.04, 7.005, 4.770, 0.42),
("OD-002", 500, 0.06, 7.012, 4.775, 0.88),
("OD-003", 1200, 0.05, 7.008, 4.768, 0.56),
("OD-004", 900, 0.05, 7.018, 4.778, 0.71),
("OD-005", 2200, 0.03, 7.001, 4.772, 0.22),
("OD-007", 2900, 0.04, 6.998, 4.766, 0.08),
("OD-009", 1700, 0.04, 7.010, 4.770, 0.34),
("OD-010", 900, 0.06, 7.020, 4.782, 0.78),
]
rows_hist, rows_snap = [], []
for name, qi, di, lon, lat, wc in wells:
rate36 = qi * np.exp(-di * months)
for m, r in zip(months, rate36):
rows_hist.append({"well": name, "month": int(m), "oil_bopd": float(r)})
rows_snap.append({"well": name, "lon": lon, "lat": lat,
"oil_bopd": float(rate36[-1]), "water_cut": wc})
hist = pd.DataFrame(rows_hist)
snapshot = pd.DataFrame(rows_snap)
fig, axes = plt.subplots(2, 2, figsize=(14, 9))
# Panel 1 - field total over time
field_total = hist.groupby("month")["oil_bopd"].sum()
axes[0, 0].plot(field_total.index, field_total.values, color="#2E8B57", linewidth=2)
axes[0, 0].set_xlabel("Month")
axes[0, 0].set_ylabel("Field oil rate (bopd)")
axes[0, 0].set_title("Field oil-rate trend")
axes[0, 0].grid(True, alpha=0.3)
# Panel 2 - top 5 by current rate
top5 = snapshot.sort_values("oil_bopd", ascending=False).head(5)
axes[0, 1].barh(top5["well"], top5["oil_bopd"], color="#4682B4")
axes[0, 1].invert_yaxis()
axes[0, 1].set_xlabel("Current oil rate (bopd)")
axes[0, 1].set_title("Top 5 wells")
# Panel 3 - water-cut distribution
axes[1, 0].hist(snapshot["water_cut"], bins=8, color="#CC4444", edgecolor="white")
axes[1, 0].set_xlabel("Water cut")
axes[1, 0].set_ylabel("Wells")
axes[1, 0].set_title("Water-cut distribution")
# Panel 4 - bubble map
sc = axes[1, 1].scatter(
snapshot["lon"], snapshot["lat"],
s=snapshot["oil_bopd"] / 4, c=snapshot["water_cut"],
cmap="RdYlGn_r", alpha=0.85, edgecolors="black", linewidths=0.6,
)
fig.colorbar(sc, ax=axes[1, 1], label="Water cut")
axes[1, 1].set_xlabel("Longitude")
axes[1, 1].set_ylabel("Latitude")
axes[1, 1].set_title("Field map (size=rate, color=water cut)")
fig.suptitle("OML 58 - monthly field report", fontweight="bold", y=1.01)
plt.tight_layout()
plt.show()
lockCopying code is a Full Access feature.