Exerciseschevron_rightChapter 4chevron_right4.9
fitness_center

Exercise 4.9

Pressure Survey Analysis

Level 3
Chapter 4: NumPy & Pandas
descriptionProblem

A pressure survey records static bottomhole pressure (SBHP) at a handful of dates over a well's life. The slope of pressure against time tells the reservoir engineer how fast the reservoir is depleting, and, extrapolated, when a well will reach abandonment pressure.

The starter builds survey (columns: well, date, sbhp_psi) for three wells, with date already parsed to datetime. Use DAYS_PER_MONTH = 30.4375 to turn date gaps into months. Write three functions:

  1. decline_rate_psi_per_month(df, well): fit a straight line to that

well's SBHP vs. months since its first survey (np.polyfit(months, sbhp, 1)) and return the slope (negative = depleting).

  1. fastest_depleting(df): the well with the steepest (most negative)

decline rate.

  1. months_to_abandonment(df, well, p_abandon=2000): from the latest

survey, how many months until the line reaches p_abandon: (p_abandon − latest_sbhp) / rate.

Then plot SBHP vs. months for all three wells, each with its fitted trend line. A few pressure points and a ruler is the cheapest depletion diagnostic in the business.

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 numpy as np
import pandas as pd
import matplotlib.pyplot as plt

survey = pd.DataFrame([
    {"well": "OD-001", "date": "2023-01-15", "sbhp_psi": 4850},
    {"well": "OD-001", "date": "2023-07-20", "sbhp_psi": 4620},
    {"well": "OD-001", "date": "2024-01-10", "sbhp_psi": 4390},
    {"well": "OD-001", "date": "2024-07-15", "sbhp_psi": 4185},
    {"well": "OD-003", "date": "2023-06-01", "sbhp_psi": 5100},
    {"well": "OD-003", "date": "2024-01-15", "sbhp_psi": 4750},
    {"well": "OD-003", "date": "2024-07-01", "sbhp_psi": 4520},
    {"well": "OD-005", "date": "2023-03-01", "sbhp_psi": 4200},
    {"well": "OD-005", "date": "2024-03-01", "sbhp_psi": 4050},
    {"well": "OD-005", "date": "2024-09-01", "sbhp_psi": 3920},
])
survey["date"] = pd.to_datetime(survey["date"])

DAYS_PER_MONTH = 30.4375
P_ABANDON = 2000


def _months_since_first(dates):
    return (dates - dates.iloc[0]).dt.days / DAYS_PER_MONTH


def decline_rate_psi_per_month(df, well):
    d = df[df["well"] == well].sort_values("date")
    months = _months_since_first(d["date"])
    slope, _intercept = np.polyfit(months, d["sbhp_psi"], 1)
    return slope


def fastest_depleting(df):
    rates = {well: decline_rate_psi_per_month(df, well) for well in df["well"].unique()}
    return min(rates, key=rates.get)


def months_to_abandonment(df, well, p_abandon=P_ABANDON):
    d = df[df["well"] == well].sort_values("date")
    latest_sbhp = d["sbhp_psi"].iloc[-1]
    return (p_abandon - latest_sbhp) / decline_rate_psi_per_month(df, well)


fig, ax = plt.subplots(figsize=(10, 6))
for well in survey["well"].unique():
    d = survey[survey["well"] == well].sort_values("date")
    months = _months_since_first(d["date"])
    slope, intercept = np.polyfit(months, d["sbhp_psi"], 1)
    ax.scatter(months, d["sbhp_psi"], label=well)
    ax.plot(months, slope * months + intercept)
    print(f"{well}: {slope:6.1f} psi/month, "
          f"{months_to_abandonment(survey, well):5.0f} months to abandonment")

ax.set_xlabel("Months since first survey")
ax.set_ylabel("SBHP (psi)")
ax.set_title("Pressure survey decline - OML 58")
ax.legend()
plt.tight_layout()
plt.show()

lockCopying code is a Full Access feature.