Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions meridian/analysis/review/checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
from typing_extensions import override
import xarray as xr


ConfigType = TypeVar("ConfigType", bound=configs.BaseConfig)
ResultType = TypeVar("ResultType", bound=results.CheckResult)

Expand Down Expand Up @@ -943,6 +942,8 @@ def run(self) -> results.ImplausibleROICheckResult:
high_roi_channels=high_roi_channels,
low_roi_channels=low_roi_channels,
aggregate_details=aggregate_details,
roi_upper_bound=self._config.roi_upper_bound,
roi_lower_bound=self._config.roi_lower_bound,
)


Expand Down Expand Up @@ -1028,6 +1029,7 @@ def run(self) -> results.HighVarianceCheckResult:
),
channel_results=channel_results,
high_variance_channels=high_variance_channels,
prior_relative_hdi_width=self._config.prior_relative_hdi_width,
)


Expand Down Expand Up @@ -1070,6 +1072,7 @@ def run(self) -> results.PotentialBiasCheckResult:
channel_results=[],
low_correlation_channels=[],
correlation_matrix=correlation_matrix,
correlation_threshold=self._config.correlation_threshold,
)

media_data = self._model_context.input_data.get_all_media_and_rf()
Expand Down Expand Up @@ -1129,5 +1132,5 @@ def run(self) -> results.PotentialBiasCheckResult:
channel_results=channel_results,
low_correlation_channels=low_correlation_channels,
correlation_matrix=correlation_matrix,
correlation_threshold=self._config.correlation_threshold,
)

24 changes: 24 additions & 0 deletions meridian/analysis/review/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,27 @@
" We recommend checking if important controls are missing or calibrating"
" these channels using an incrementality experiment to address this."
)

# Chart and table channel colors
CHANNEL_COLORS: tuple[str, ...] = (
"#185abc",
"#b31412",
"#ea8600",
"#137333",
"#c26401",
"#b80672",
"#7627bb",
"#098591",
"#669df6",
"#ee675c",
"#fcc934",
"#5bb974",
"#fa903e",
"#ff63b8",
"#af5cf7",
"#4ecde6",
"#8f4e06",
"#041e49",
"#c4cce1",
"#8d0053",
)
15 changes: 13 additions & 2 deletions meridian/analysis/review/results.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@
import enum
import functools
import os
from typing import Any

from typing import Any, TypeVar
import jinja2
from meridian.analysis import summary_text
from meridian.analysis.review import configs
Expand Down Expand Up @@ -104,6 +103,9 @@ def recommendation(self) -> str:
return report_str


_T = TypeVar("_T", bound=CheckResult)


# ==============================================================================
# Check: Convergence
# ==============================================================================
Expand Down Expand Up @@ -683,6 +685,8 @@ class ImplausibleROICheckResult(CheckResult):
high_roi_channels: list[str]
low_roi_channels: list[str]
aggregate_details: Mapping[str, Any]
roi_upper_bound: float = configs.ImplausibleROIConfig.roi_upper_bound
roi_lower_bound: float = configs.ImplausibleROIConfig.roi_lower_bound

@property
def details(self) -> Mapping[str, Any]:
Expand Down Expand Up @@ -764,11 +768,15 @@ class HighVarianceCheckResult(CheckResult):
channel_results: A list of `HighVarianceChannelResult` for each channel.
high_variance_channels: A list of channel names flagged as having high ROI
variance.
prior_relative_hdi_width: The prior relative HDI width.
"""

case: HighVarianceAggregateCases
channel_results: list[HighVarianceChannelResult]
high_variance_channels: list[str]
prior_relative_hdi_width: float = (
constants.PRIOR_RELATIVE_HDI_WIDTH_FOR_80_PERCENT
)

@property
def details(self) -> Mapping[str, Any]:
Expand Down Expand Up @@ -851,6 +859,9 @@ class PotentialBiasCheckResult(CheckResult):
channel_results: list[PotentialBiasChannelResult]
low_correlation_channels: list[str]
correlation_matrix: xr.DataArray
correlation_threshold: float = (
configs.PotentialBiasConfig.correlation_threshold
)

@property
def details(self) -> Mapping[str, Any]:
Expand Down
89 changes: 89 additions & 0 deletions meridian/analysis/review/results_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,95 @@ def test_potential_bias_check_result(
result.details[review_constants.CORRELATION_MATRIX], corr_matrix
)

def test_generate_potential_bias_chart_json(self):
corr_matrix = xr.DataArray(
np.array([
[[0.05, 0.15], [0.08, 0.02]],
[[0.12, 0.04], [0.01, 0.09]],
[[0.02, 0.18], [0.06, 0.11]],
]),
coords={
constants.GEO: ["geo1", "geo2", "geo3"],
constants.CHANNEL: ["channel1", "channel2"],
constants.CONTROL_VARIABLE: ["control1", "control2"],
},
dims=[
constants.GEO,
constants.CHANNEL,
constants.CONTROL_VARIABLE,
],
)
mock_potential_bias = results.PotentialBiasCheckResult(
case=results.PotentialBiasAggregateCases.REVIEW,
channel_results=[
results.PotentialBiasChannelResult(
case=results.PotentialBiasChannelCases.LOW_CORRELATION,
channel_name="channel1",
max_abs_correlation=0.18,
),
results.PotentialBiasChannelResult(
case=results.PotentialBiasChannelCases.LOW_CORRELATION,
channel_name="channel2",
max_abs_correlation=0.11,
),
],
low_correlation_channels=["channel1", "channel2"],
correlation_matrix=corr_matrix,
correlation_threshold=0.10,
)
summary = results.ReviewSummary(
overall_status=results.Status.PASS,
summary_message="Passed",
results=[mock_potential_bias],
health_score=90.0,
)
chart_json = summary._generate_potential_bias_chart_json(
mock_potential_bias
)
chart_json_no_args = summary._generate_potential_bias_chart_json()
self.assertEqual(chart_json, chart_json_no_args)
self.assertIsNotNone(chart_json)
chart_dict = json.loads(chart_json)

self.assertIn("layer", chart_dict)
layers = chart_dict["layer"]

geos_layer = None
max_layer = None
for layer in layers:
if layer.get("mark", {}).get("type") == "point":
if layer.get("mark", {}).get("shape") == "diamond":
max_layer = layer
else:
geos_layer = layer

self.assertIsNotNone(geos_layer)
self.assertIsNotNone(max_layer)

self.assertEqual(
geos_layer["encoding"]["y"]["field"], review_constants.PAIR
)
self.assertEqual(max_layer["encoding"]["y"]["field"], review_constants.PAIR)

self.assertIn("channel1 vs. control1", chart_json)
self.assertNotIn("channel1 - control1", chart_json)

all_pairs = []
if "datasets" in chart_dict:
for dataset in chart_dict["datasets"].values():
for row in dataset:
if isinstance(row, dict) and review_constants.PAIR in row:
all_pairs.append(row[review_constants.PAIR])
self.assertIn("channel1 vs. control1", all_pairs)

geos_domain = geos_layer["encoding"]["x"]["scale"]["domain"]
max_domain = max_layer["encoding"]["x"]["scale"]["domain"]

self.assertAlmostEqual(geos_domain[0], -0.23)
self.assertAlmostEqual(geos_domain[1], 0.23)
self.assertAlmostEqual(max_domain[0], -0.23)
self.assertAlmostEqual(max_domain[1], 0.23)


if __name__ == "__main__":
absltest.main()
26 changes: 26 additions & 0 deletions meridian/templates/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -624,4 +624,30 @@ chip.calibrated-wide {
justify-content: center;
box-sizing: border-box;
}

.plots-section {
display: flex;
flex-direction: column;
row-gap: 40px;
padding: 32px;
}

.plot-container {
display: flex;
flex-direction: column;
row-gap: 16px;

.plot-title {
margin: 0;
font-family: $google_sans;
font-size: 20px;
font-weight: 500;
color: $title_dark_grey;
}

warnings {
margin: 0 !important;
}
}

// copybara:strip_end
Loading