Skip to content

Commit

Permalink
Statistical options in WellAnalysis (#1199)
Browse files Browse the repository at this point in the history
* Added error bars in well overview bar chart

* Added water and gas injection as responses

* Implemented statistics options

* New settings group statistical options

* More work on statistics options

* Deactivated error bars if stat option p10_minus_p90 is selected

* Changelog entry

---------

Co-authored-by: Øyvind Lind-Johansen <[email protected]>
  • Loading branch information
lindjoha and Øyvind Lind-Johansen authored Mar 17, 2023
1 parent 8dcb3c4 commit 3d76d6b
Show file tree
Hide file tree
Showing 9 changed files with 196 additions and 21 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [#1195](https://github.com/equinor/webviz-subsurface/pull/1195) - `RftPlotter` faultlines argument can now use fault polygons csv file with *X, Y, ID* header (fmu-dataio default)
- [#1196](https://github.com/equinor/webviz-subsurface/pull/1196) - `SwatinitQC` faultlines argument can now use fault polygons csv file with *X, Y, ID* header (fmu-dataio default)

### Added
- [#1199](https://github.com/equinor/webviz-subsurface/pull/1199) - Added more statistical options to the WellOverview tab in `WellAnalysis`, and the possibility to see injection rates.

## [0.2.17] - 2023-01-18

### Changed
Expand Down
10 changes: 10 additions & 0 deletions webviz_subsurface/plugins/_well_analysis/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,13 @@ class ChartType(StrEnum):
BAR = "bar"
PIE = "pie"
AREA = "area"


class StatType(StrEnum):
MEAN = "mean"
P10 = "p10"
P50 = "p50"
P90 = "p90"
MAX = "max"
MIN = "min"
P10_MINUS_P90 = "p10-p90"
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ def add_network_pressure_traces(
"type": "scatter",
"x": [],
"y": [],
"line": dict(color=next(color_iterator)),
"line": {"color": next(color_iterator)},
"name": label,
"showlegend": True,
"hovertext": (f"{label}"),
Expand Down Expand Up @@ -249,7 +249,7 @@ def add_area_trace(
mode="lines",
# fill="tonexty",
# fillcolor=color,
line=dict(width=linewidth, color=color),
line={"width": linewidth, "color": color},
name=name,
text=name,
stackgroup="one",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
from ._filters import WellOverviewFilters
from ._layout_options import WellOverviewLayoutOptions
from ._selections import WellOverviewSelections
from ._statistical_options import WellOverviewStatisticalOptions
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ def layout(self) -> List[Component]:
"label": "White background",
"value": "white_background",
},
{
"label": "Error bars (P10/P90)",
"value": "errorbars",
"disabled": False,
},
],
value=["legend"],
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ def layout(self) -> List[Component]:
{"label": "Oil production", "value": "WOPT"},
{"label": "Gas production", "value": "WGPT"},
{"label": "Water production", "value": "WWPT"},
{"label": "Water injection", "value": "WWIT"},
{"label": "Gas injection", "value": "WGIT"},
],
value="WOPT",
multi=False,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from typing import List

import webviz_core_components as wcc
from dash import html
from dash.development.base_component import Component
from webviz_config.utils import StrEnum
from webviz_config.webviz_plugin_subclasses import SettingsGroupABC

from ...._types import StatType


class WellOverviewStatisticalOptions(SettingsGroupABC):
class Ids(StrEnum):
STATISTICS = "statistics"

def __init__(self) -> None:
super().__init__("Statistics")

def layout(self) -> List[Component]:
return [
html.Div(
children=[
wcc.RadioItems(
id=self.register_component_unique_id(self.Ids.STATISTICS),
options=[
{"label": "Mean", "value": StatType.MEAN},
{"label": "P10 (high)", "value": StatType.P10},
{
"label": "P50 (median)",
"value": StatType.P50,
},
{"label": "P90 (low)", "value": StatType.P90},
{"label": "Maximum", "value": StatType.MAX},
{"label": "Minimum", "value": StatType.MIN},
{"label": "P10 - P90", "value": StatType.P10_MINUS_P90},
],
value=StatType.MEAN,
)
],
)
]
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from plotly.subplots import make_subplots
from webviz_config import WebvizConfigTheme

from ...._types import ChartType
from ...._types import ChartType, StatType
from ...._utils import EnsembleWellAnalysisData


Expand All @@ -21,6 +21,7 @@ def __init__(
prod_from_date: Union[datetime.datetime, None],
prod_until_date: Union[datetime.datetime, None],
charttype: ChartType,
stattype: StatType,
wells: List[str],
theme: WebvizConfigTheme,
) -> None:
Expand All @@ -31,6 +32,7 @@ def __init__(
self._prod_from_date = prod_from_date
self._prod_until_date = prod_until_date
self._charttype = charttype
self._stattype = stattype
self._wells = wells
self._colors = theme.plotly_theme["layout"]["colorway"]
self._rows, self._cols = self.get_subplot_dim()
Expand Down Expand Up @@ -78,16 +80,44 @@ def _get_ensemble_charttype_data(self, ensemble: str) -> pd.DataFrame:
prod_until_date=self._prod_until_date,
)
df = df[df["WELL"].isin(self._wells)]
df_mean = df.groupby("WELL").mean().reset_index()
return df_mean[df_mean[self._sumvec] > 0]
return df

# else chart type == area
df = self._data_models[ensemble].get_summary_data(
well_sumvec=self._sumvec,
prod_from_date=self._prod_from_date,
prod_until_date=self._prod_until_date,
)
return df.groupby("DATE").mean().reset_index()
return df

def _calc_statistics(
self, df: pd.DataFrame, groupby: str, stattype: StatType
) -> pd.DataFrame:
"""Calculates statistics from input dataframe"""
df_grouped = df.groupby(groupby)

if stattype == StatType.MEAN:
df_out = df_grouped.mean(numeric_only=True).reset_index()
if stattype == StatType.P50:
df_out = df_grouped.quantile(0.5, numeric_only=True).reset_index()
if stattype == StatType.P10:
df_out = df_grouped.quantile(0.9, numeric_only=True).reset_index()
if stattype == StatType.P90:
df_out = df_grouped.quantile(0.1, numeric_only=True).reset_index()
if stattype == StatType.MAX:
df_out = df_grouped.max(0.1).reset_index()
if stattype == StatType.MIN:
df_out = df_grouped.min(0.1).reset_index()
if stattype == StatType.P10_MINUS_P90:
df_p10 = self._calc_statistics(df, groupby, StatType.P10)
df_p90 = self._calc_statistics(df, groupby, StatType.P90)
df_merged = df_p10.merge(df_p90, on=groupby)
for col in df_p10.columns:
if col in ["DATE", "REAL", groupby]:
continue
df_merged[col] = df_merged[f"{col}_x"] - df_merged[f"{col}_y"]
df_out = df_merged
return df_out

def _add_traces(self) -> None:
"""Add all traces for the currently selected chart type."""
Expand All @@ -97,10 +127,12 @@ def _add_traces(self) -> None:
df = self._get_ensemble_charttype_data(ensemble)

if self._charttype == ChartType.PIE:
df_stat = self._calc_statistics(df, "WELL", self._stattype)
df_stat = df_stat[df_stat[self._sumvec] > 0]
self._figure.add_trace(
go.Pie(
values=df[self._sumvec],
labels=df["WELL"],
values=df_stat[self._sumvec],
labels=df_stat["WELL"],
marker_colors=self._colors,
textposition="inside",
texttemplate="%{label}",
Expand All @@ -110,24 +142,44 @@ def _add_traces(self) -> None:
)

elif self._charttype == ChartType.BAR:
df_stat = self._calc_statistics(df, "WELL", self._stattype)
df_stat = df_stat[df_stat[self._sumvec] > 0]

trace = {
"x": df["WELL"],
"y": df[self._sumvec],
"x": df_stat["WELL"],
"y": df_stat[self._sumvec],
"orientation": "v",
"type": "bar",
"name": ensemble,
"marker": {"color": self._colors[i]},
"text": df[self._sumvec],
"text": df_stat[self._sumvec],
"textposition": "none",
"texttemplate": "%{text:.2s}",
}

if self._stattype is not StatType.P10_MINUS_P90:
# Add error bars
wells = df_stat["WELL"].unique()
df_p10 = self._calc_statistics(df, "WELL", StatType.P10)
df_p10 = df_p10[df_p10["WELL"].isin(wells)]
df_p90 = self._calc_statistics(df, "WELL", StatType.P90)
df_p90 = df_p90[df_p90["WELL"].isin(wells)]
trace["error_y"] = {
"type": "data",
"symmetric": False,
"array": df_p10[self._sumvec] - df_stat[self._sumvec],
"arrayminus": df_stat[self._sumvec] - df_p90[self._sumvec],
"visible": False,
}

self._figure.add_trace(
trace,
row=1,
col=1,
)

elif self._charttype == ChartType.AREA:
df_stat = self._calc_statistics(df, "DATE", self._stattype)
color_iterator = itertools.cycle(self._colors)

for well in self._data_models[ensemble].wells:
Expand All @@ -139,14 +191,14 @@ def _add_traces(self) -> None:

self._figure.add_trace(
go.Scatter(
x=df["DATE"],
y=df[f"{self._sumvec}:{well}"],
x=df_stat["DATE"],
y=df_stat[f"{self._sumvec}:{well}"],
hoverinfo="text+x+y",
hoveron="fills",
mode="lines",
stackgroup="one",
name=well,
line=dict(width=0.1, color=next(color_iterator)),
line={"width": 0.1, "color": next(color_iterator)},
legendgroup="Wells",
showlegend=showlegend,
),
Expand All @@ -158,6 +210,7 @@ def _add_traces(self) -> None:
def format_well_overview_figure(
figure: go.Figure,
charttype: ChartType,
stattype: StatType,
settings: List[str],
sumvec: str,
prod_from_date: Union[str, None],
Expand Down Expand Up @@ -186,19 +239,40 @@ def format_well_overview_figure(
figure.update_traces(
textposition=("auto" if "show_prod_text" in settings else "none")
)
if stattype == StatType.P10_MINUS_P90:
# Error bars doesn't make sens for the P10 - P90 option.
figure.update_traces(error_y={"visible": False})
else:
figure.update_traces(error_y={"visible": "errorbars" in settings})

# These are valid for all chart types
figure.update_layout(
template=("plotly_white" if "white_background" in settings else "plotly")
)

# Make title
phase = {"WOPT": "Oil", "WGPT": "Gas", "WWPT": "Water"}[sumvec]
title = f"Cumulative Well {phase} Production (Sm3)"
phase = {
"WOPT": "Oil Production",
"WGPT": "Gas Production",
"WWPT": "Water Production",
"WWIT": "Water Injection",
"WGIT": "Gas Injection",
}[sumvec]
title = f"Cumulative Well {phase} (Sm3)"
if prod_from_date is not None:
title += f" from {prod_from_date}"
if prod_until_date is not None:
title += f" until {prod_until_date}"
stattype_text = {
StatType.MEAN: "Mean",
StatType.P10: "P10",
StatType.P50: "P50",
StatType.P90: "P90",
StatType.MAX: "Maximum",
StatType.MIN: "Minimum",
StatType.P10_MINUS_P90: "P10-P90",
}
title += f" - {stattype_text[stattype]} of all realizations"

figure.update(
layout_title_text=title,
Expand Down
Loading

0 comments on commit 3d76d6b

Please sign in to comment.