Exerciseschevron_rightChapter 3chevron_right3.9
fitness_center

Exercise 3.9

Formation Tops Database

Level 2
Chapter 3: Data Structures
descriptionProblem

Geologists track formation tops: the depth at which each formation is first encountered in each well. The natural structure is a dictionary of dictionaries: each well maps to {formation: top_depth_ft}. The starter gives you four OML 58 wells. Note that OD-007 never reached the E3000 sand; its dict simply omits that key.

Write three functions:

  1. wells_with_formation(db, formation): the names of all wells that

penetrated a given formation.

  1. average_top_depth(db, formation): the mean top depth of a formation

across the wells that have it (skip wells that don't).

  1. formation_at_depth(db, well, depth_ft): which formation you are in at

a given depth in a given well: the formation with the deepest top that is still at or above depth_ft. Return None if depth_ft is shallower than the first formation top.

Then draw a correlation chart: formation top depth on the y-axis (with depth increasing downward, invert the axis), wells along the x-axis, one line connecting each formation across the wells. This side-by-side top correlation is one of the most common pictures in subsurface geology.

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 matplotlib.pyplot as plt

formation_tops = {
    "OD-001": {"Benin": 200, "Agbada": 3800, "Akata": 8200, "E3000": 9400},
    "OD-003": {"Benin": 180, "Agbada": 3650, "Akata": 8050, "E3000": 9150},
    "OD-005": {"Benin": 220, "Agbada": 3900, "Akata": 8400, "E3000": 9600},
    "OD-007": {"Benin": 160, "Agbada": 3600, "Akata": 7950},
}


def wells_with_formation(db, formation):
    return [well for well, tops in db.items() if formation in tops]


def average_top_depth(db, formation):
    depths = [tops[formation] for tops in db.values() if formation in tops]
    return sum(depths) / len(depths) if depths else None


def formation_at_depth(db, well, depth_ft):
    candidates = [(fm, top) for fm, top in db[well].items() if top <= depth_ft]
    if not candidates:
        return None
    return max(candidates, key=lambda pair: pair[1])[0]


# Correlation chart - depth downward, one line per formation across wells.
well_names = list(formation_tops.keys())
x = list(range(len(well_names)))

fig, ax = plt.subplots(figsize=(10, 6))
for formation in ["Benin", "Agbada", "Akata", "E3000"]:
    xs = [i for i, w in enumerate(well_names) if formation in formation_tops[w]]
    ys = [formation_tops[w][formation] for w in well_names if formation in formation_tops[w]]
    ax.plot(xs, ys, marker="o", linewidth=2, label=formation)

ax.set_xticks(x)
ax.set_xticklabels(well_names)
ax.set_xlabel("Well")
ax.set_ylabel("Depth (ft)")
ax.invert_yaxis()
ax.legend()
ax.set_title("Formation tops correlation - OML 58")
plt.tight_layout()
plt.show()

lockCopying code is a Full Access feature.