A Python library for tornado chart generation and analysis
Project description
TornadoPy
A Python library for fast tornado, distribution, and correlation plots from uncertainty-analysis results exported from SLB Petrel.
TornadoPy uses Polars for data handling and Matplotlib for publication-quality charts.
Installation
pip install tornadopy
Quick start
from tornadopy import Dataset, tornado_plot, distribution_plot, correlation_plot
# 1. Load the Excel workbook into a dataset
ds = Dataset("uncertainty_results.xlsx")
# 2. (Optional) define reusable filter presets
# A filter is a spatial selection only — never include 'property' here.
ds.set_filter("north", {"zone": ["north_main", "north_flank"]})
# 3. Plot — the plot function decides which parameter (sheet) and property to use
fig, ax, _ = tornado_plot(
ds, property="stoiip", filters="north", title="STOIIP sensitivity", unit="MM bbl"
)
fig, ax, _ = distribution_plot(
ds, parameter="NetPay", property="stoiip", filters="north"
)
fig, ax, _ = correlation_plot(
ds, parameter="Full_Uncertainty", filters="north",
variables=["NetPay", "Porosity", "NTG"],
)
API mental model
Dataset the dataset
└─ holds: data + filter presets + introspection
└─ no opinions on which property or sheet to plot
tornado_plot / distribution_plot / correlation_plot
└─ accept the dataset
└─ accept property and (where relevant) parameter/sheet
└─ accept filters as either a stored preset name or an inline dict
Inspecting the dataset
ds.parameters() # ['Full_Uncertainty', 'NetPay', ...]
ds.properties("Full_Uncertainty") # ['stoiip', 'giip', ...]
ds.unique_values("zone", "Full_Uncertainty")
ds.show_filters("Full_Uncertainty")
# {'zone': ['north_main', 'north_flank', ...], 'contact_segment': [...]}
ds.show_parameters()
# {
# 'Full_Uncertainty': {
# 'n_cases': 1854,
# 'properties': ['stoiip', 'giip'],
# 'filters': {'zone': [...], 'contact_segment': [...]},
# 'is_base_case': False,
# },
# 'NetPay': {...},
# }
ds.describe() # Pretty-printed overview + usage examples
Filters
A filter is a dict of dynamic-field selections. The spatial fields (zones,
segments, boundaries) come from your Excel header rows. The property key is
not allowed — pass property to the plot or compute call instead.
# Inline filter
tornado_plot(ds, property="stoiip", filters={"zone": "north_main"})
# Multiple values aggregate
distribution_plot(
ds, parameter="NetPay", property="stoiip",
filters={"zone": ["north_main", "north_flank"]},
)
# Stored presets — reuse by name
ds.set_filters({
"north": {"zone": ["north_main", "north_flank"]},
"south": {"zone": ["south_main", "south_flank"]},
})
ds.list_filters() # ['north', 'south']
ds.get_filter("north") # {'zone': [...]}
# Active filter — applied to every plot/compute call that doesn't pass `filters=`
# `filter()` is chainable: it sets and returns the dataset.
ds.filter({"contact_regions": ["cerisa main"]}) # set inline
ds.filter("north") # set from a stored preset
ds.filter(None) # clear
ds.active_filter # read current
tornado_plot(ds, property="stoiip") # uses the active filter
tornado_plot(ds, property="stoiip", filters="south") # explicit override
distribution_plot(ds.filter({"zone": "x"}), property="stoiip") # one-liner chain
Default parameter
distribution_plot and correlation_plot need a parameter (sheet). If you
omit it, the first sheet is used and a warning is printed listing all available
parameters. tornado_plot does not take parameter — a tornado chart is
inherently across all sheets.
Base / reference cases
ds.base_case() # full (unfiltered) base case as a Case
ds.base_case(filters="north") # volumes summed over the 'north' segments
ds.base_case("stoiip", filters="north") # focus on one property
ds.ref_case("stoiip", filters="north") # same, for the reference case
bc = ds.base_case(filters="north")
bc.properties() # {'stoiip': ..., 'giip': ...}
bc["stoiip"] # a single volume
ds.filter("north").base_case() # a FilteredDataset applies its filter
Both property and filters are optional — called bare, base_case() /
ref_case() return the full unfiltered case. The base / reference sheet is set
at construction time (base_case="Base_case" by default). Row 0 = base;
row 1 = reference.
Extracting a case by percentile
extract_case returns the Case whose property value is closest to a
percentile or summary statistic. The result is a real realisation from the
sheet — printable, and with variable/metadata access.
# Single case — the realisation nearest the median stoiip
case = ds.extract_case("stoiip", parameter="NTGseed", percentile=50)
print(case) # Case NTGseed_<idx> (p50) + stoiip, giip, ... + selection info
case.var("NTGseed") # a $-prefixed variable value
case.variables() # every variable on the case
case.properties() # {'stoiip': ..., 'giip': ...}
case.idx, case.type # row index, "p50"
case.selection_info # {'selection_values': {'stoiip_target': ..., 'stoiip_actual': ...}, ...}
# Several at once — pass a list, get a list back
p10, p50, p90 = ds.extract_case("stoiip", parameter="NTGseed", percentile=[10, 50, 90])
# Named stats instead of a percentile
hi = ds.extract_case("stoiip", parameter="NTGseed", stat="max")
lo = ds.extract_case("stoiip", parameter="NTGseed", stat=["min", "mean"])
percentile is the literal percentile (90 = high value), and the match is
the realisation nearest the interpolated target. filters scopes which
segments are summed before ranking. For multi-property weighted selection use
compute(..., case_selection=True) instead.
Statistics (raw)
For numerical work without plotting, use compute and compute_batch directly.
Same rule: property is a kwarg, not a filter key.
ds.compute("p90p10", parameter="NetPay", property="stoiip", filters="north")
ds.compute_batch("p90p10", property="stoiip", filters="north") # all sheets
Available stats: p90p10, minmax, p1p99, p25p75, mean, median,
std, cv, sum, count, variance, range, percentile
(options={"p": 75}), distribution.
Case selection
Find representative cases that best match statistical targets:
fig, ax, _ = tornado_plot(
ds, property="stoiip", filters="north",
case_selection=True,
selection_criteria={"stoiip": 0.6, "giip": 0.4},
)
selection_criteria keys can be:
- a property name → uses the call's main filter
- a stored-filter name → uses that filter's spatial fields plus its name as the
property (the
'property'ban applies; if you need different properties per zone set, use the explicitcombinationsform)
ds.set_filter("north", {"zone": ["north_main", "north_flank"]})
ds.set_filter("south", {"zone": ["south_main"]})
tornado_plot(
ds, property="stoiip", filters="north",
case_selection=True,
selection_criteria={
"combinations": [
{"filters": "north", "properties": {"stoiip": 0.5, "giip": 0.2}},
{"filters": "south", "properties": {"stoiip": 0.3}},
]
},
)
Excel layout
Each parameter is one sheet:
Metadata rows (optional):
Key: Value
Header block (one or more rows, combined automatically):
Zone Segment Property
north main stoiip north flank stoiip south main stoiip
Case marker:
Case Case Case ...
Data rows:
Case1 123.4 456.7 ...
Case2 125.1 458.2 ...
Rules:
- The "Case" row's first column is the literal string
Case. - Headers above it define columns; multiple header rows are combined.
- The data block follows the Case row; one row per uncertainty case.
- Each parameter is a separate sheet.
- Base-case sheet (default
"Base_case"): row 0 = base, row 1 = reference.
Plot styling
Each plot function accepts a settings dict to override defaults — colors,
fonts, gridlines, etc. See the docstrings for keys.
tornado_plot(
ds, property="stoiip",
settings={
"figsize": (12, 8),
"pos_dark": "#2E5BFF",
"neg_dark": "#E74C3C",
"show_percentage_diff": True,
},
)
Requirements
- Python ≥ 3.9
- numpy ≥ 1.20
- polars ≥ 0.18
- fastexcel ≥ 0.9
- matplotlib ≥ 3.5
License
MIT — see LICENSE.
Issues / contributions
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tornadopy-0.1.75.tar.gz.
File metadata
- Download URL: tornadopy-0.1.75.tar.gz
- Upload date:
- Size: 65.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7b0b8a71bbc25b6f7bfd413b945f95925cd6596e7517c59818be66e52730f718
|
|
| MD5 |
56a5a53ea4f20bb60c9a33904d1d79b4
|
|
| BLAKE2b-256 |
82dadc82eeb9f06396208e1c9e1b33fc540606d94923fc9467132375233a5f82
|
File details
Details for the file tornadopy-0.1.75-py3-none-any.whl.
File metadata
- Download URL: tornadopy-0.1.75-py3-none-any.whl
- Upload date:
- Size: 64.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2903ba1f8d70e43582c64b349b1ae56189f3ed5a63560fae47dd1441960aee8b
|
|
| MD5 |
6a2462015253a01dbf117347bf3c6408
|
|
| BLAKE2b-256 |
3d80ade6e87ef188c2bf25a7efb9eff7e2fd28be907e690beb7fff250129306d
|