google-meridian 1.0.8__py3-none-any.whl → 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {google_meridian-1.0.8.dist-info → google_meridian-1.1.0.dist-info}/METADATA +2 -2
- google_meridian-1.1.0.dist-info/RECORD +41 -0
- {google_meridian-1.0.8.dist-info → google_meridian-1.1.0.dist-info}/WHEEL +1 -1
- meridian/__init__.py +1 -1
- meridian/analysis/analyzer.py +303 -207
- meridian/analysis/optimizer.py +431 -82
- meridian/analysis/summarizer.py +25 -7
- meridian/analysis/test_utils.py +81 -81
- meridian/analysis/visualizer.py +81 -39
- meridian/constants.py +111 -26
- meridian/data/input_data.py +115 -19
- meridian/data/test_utils.py +116 -5
- meridian/data/time_coordinates.py +3 -3
- meridian/model/media.py +133 -98
- meridian/model/model.py +457 -52
- meridian/model/model_test_data.py +11 -0
- meridian/model/posterior_sampler.py +120 -43
- meridian/model/prior_distribution.py +95 -29
- meridian/model/prior_sampler.py +179 -209
- meridian/model/spec.py +196 -36
- meridian/model/transformers.py +15 -3
- google_meridian-1.0.8.dist-info/RECORD +0 -41
- {google_meridian-1.0.8.dist-info → google_meridian-1.1.0.dist-info}/licenses/LICENSE +0 -0
- {google_meridian-1.0.8.dist-info → google_meridian-1.1.0.dist-info}/top_level.txt +0 -0
meridian/analysis/summarizer.py
CHANGED
|
@@ -75,8 +75,8 @@ class Summarizer:
|
|
|
75
75
|
self,
|
|
76
76
|
filename: str,
|
|
77
77
|
filepath: str,
|
|
78
|
-
start_date: tc.Date
|
|
79
|
-
end_date: tc.Date
|
|
78
|
+
start_date: tc.Date = None,
|
|
79
|
+
end_date: tc.Date = None,
|
|
80
80
|
):
|
|
81
81
|
"""Generates and saves the HTML results summary output.
|
|
82
82
|
|
|
@@ -93,8 +93,8 @@ class Summarizer:
|
|
|
93
93
|
|
|
94
94
|
def _gen_model_results_summary(
|
|
95
95
|
self,
|
|
96
|
-
start_date: tc.Date
|
|
97
|
-
end_date: tc.Date
|
|
96
|
+
start_date: tc.Date = None,
|
|
97
|
+
end_date: tc.Date = None,
|
|
98
98
|
) -> str:
|
|
99
99
|
"""Generate HTML results summary output (as sanitized content str)."""
|
|
100
100
|
all_dates = self._meridian.input_data.time_coordinates.all_dates
|
|
@@ -167,7 +167,9 @@ class Summarizer:
|
|
|
167
167
|
self._create_model_fit_card_html(
|
|
168
168
|
template_env, selected_times=selected_times
|
|
169
169
|
),
|
|
170
|
-
self._create_outcome_contrib_card_html(
|
|
170
|
+
self._create_outcome_contrib_card_html(
|
|
171
|
+
template_env, media_summary, selected_times=selected_times
|
|
172
|
+
),
|
|
171
173
|
self._create_performance_breakdown_card_html(
|
|
172
174
|
template_env, media_summary
|
|
173
175
|
),
|
|
@@ -267,16 +269,30 @@ class Summarizer:
|
|
|
267
269
|
self,
|
|
268
270
|
template_env: jinja2.Environment,
|
|
269
271
|
media_summary: visualizer.MediaSummary,
|
|
272
|
+
selected_times: Sequence[str] | None,
|
|
270
273
|
) -> str:
|
|
271
274
|
"""Creates the HTML snippet for the Outcome Contrib card."""
|
|
272
275
|
outcome = self._kpi_or_revenue()
|
|
273
276
|
|
|
277
|
+
num_selected_times = (
|
|
278
|
+
self._meridian.n_times
|
|
279
|
+
if selected_times is None
|
|
280
|
+
else len(selected_times)
|
|
281
|
+
)
|
|
282
|
+
time_granularity = (
|
|
283
|
+
c.WEEKLY
|
|
284
|
+
if num_selected_times < c.QUARTERLY_SUMMARY_THRESHOLD_WEEKS
|
|
285
|
+
else c.QUARTERLY
|
|
286
|
+
)
|
|
287
|
+
|
|
274
288
|
channel_contrib_area_chart = formatter.ChartSpec(
|
|
275
289
|
id=summary_text.CHANNEL_CONTRIB_BY_TIME_CHART_ID,
|
|
276
290
|
description=summary_text.CHANNEL_CONTRIB_BY_TIME_CHART_DESCRIPTION.format(
|
|
277
291
|
outcome=outcome
|
|
278
292
|
),
|
|
279
|
-
chart_json=media_summary.plot_channel_contribution_area_chart(
|
|
293
|
+
chart_json=media_summary.plot_channel_contribution_area_chart(
|
|
294
|
+
time_granularity=time_granularity
|
|
295
|
+
).to_json(),
|
|
280
296
|
)
|
|
281
297
|
|
|
282
298
|
channel_contrib_bump_chart = formatter.ChartSpec(
|
|
@@ -284,7 +300,9 @@ class Summarizer:
|
|
|
284
300
|
description=summary_text.CHANNEL_CONTRIB_RANK_CHART_DESCRIPTION.format(
|
|
285
301
|
outcome=outcome
|
|
286
302
|
),
|
|
287
|
-
chart_json=media_summary.plot_channel_contribution_bump_chart(
|
|
303
|
+
chart_json=media_summary.plot_channel_contribution_bump_chart(
|
|
304
|
+
time_granularity=time_granularity
|
|
305
|
+
).to_json(),
|
|
288
306
|
)
|
|
289
307
|
channel_drivers_chart = formatter.ChartSpec(
|
|
290
308
|
id=summary_text.CHANNEL_DRIVERS_CHART_ID,
|
meridian/analysis/test_utils.py
CHANGED
|
@@ -1515,13 +1515,13 @@ INC_OUTCOME_NON_MEDIA_FIXED = np.array([
|
|
|
1515
1515
|
[
|
|
1516
1516
|
1.991e02,
|
|
1517
1517
|
5.116e02,
|
|
1518
|
-
5.
|
|
1518
|
+
5.191e01,
|
|
1519
1519
|
6.623e02,
|
|
1520
1520
|
1.969e02,
|
|
1521
|
-
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
-
|
|
1521
|
+
-2.250e05,
|
|
1522
|
+
1.676e04,
|
|
1523
|
+
1.058e04,
|
|
1524
|
+
-4.475e03,
|
|
1525
1525
|
],
|
|
1526
1526
|
[
|
|
1527
1527
|
1.980e02,
|
|
@@ -1529,10 +1529,10 @@ INC_OUTCOME_NON_MEDIA_FIXED = np.array([
|
|
|
1529
1529
|
5.190e01,
|
|
1530
1530
|
6.690e02,
|
|
1531
1531
|
1.947e02,
|
|
1532
|
-
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
-
|
|
1532
|
+
-2.190e05,
|
|
1533
|
+
1.669e04,
|
|
1534
|
+
1.005e04,
|
|
1535
|
+
-4.375e03,
|
|
1536
1536
|
],
|
|
1537
1537
|
[
|
|
1538
1538
|
1.984e02,
|
|
@@ -1540,10 +1540,10 @@ INC_OUTCOME_NON_MEDIA_FIXED = np.array([
|
|
|
1540
1540
|
5.186e01,
|
|
1541
1541
|
6.678e02,
|
|
1542
1542
|
1.950e02,
|
|
1543
|
-
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
-
|
|
1543
|
+
-2.191e05,
|
|
1544
|
+
1.654e04,
|
|
1545
|
+
1.011e04,
|
|
1546
|
+
-4.644e03,
|
|
1547
1547
|
],
|
|
1548
1548
|
[
|
|
1549
1549
|
1.984e02,
|
|
@@ -1551,10 +1551,10 @@ INC_OUTCOME_NON_MEDIA_FIXED = np.array([
|
|
|
1551
1551
|
5.193e01,
|
|
1552
1552
|
6.658e02,
|
|
1553
1553
|
1.899e02,
|
|
1554
|
-
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
-
|
|
1554
|
+
-2.070e05,
|
|
1555
|
+
1.575e04,
|
|
1556
|
+
9.798e03,
|
|
1557
|
+
-4.228e03,
|
|
1558
1558
|
],
|
|
1559
1559
|
[
|
|
1560
1560
|
1.980e02,
|
|
@@ -1562,10 +1562,10 @@ INC_OUTCOME_NON_MEDIA_FIXED = np.array([
|
|
|
1562
1562
|
5.196e01,
|
|
1563
1563
|
6.697e02,
|
|
1564
1564
|
1.898e02,
|
|
1565
|
-
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
-
|
|
1565
|
+
-2.111e05,
|
|
1566
|
+
1.595e04,
|
|
1567
|
+
9.621e03,
|
|
1568
|
+
-4.596e03,
|
|
1569
1569
|
],
|
|
1570
1570
|
[
|
|
1571
1571
|
1.960e02,
|
|
@@ -1573,10 +1573,10 @@ INC_OUTCOME_NON_MEDIA_FIXED = np.array([
|
|
|
1573
1573
|
5.181e01,
|
|
1574
1574
|
6.764e02,
|
|
1575
1575
|
1.999e02,
|
|
1576
|
-
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
-
|
|
1576
|
+
-2.153e05,
|
|
1577
|
+
1.596e04,
|
|
1578
|
+
9.384e03,
|
|
1579
|
+
-3.718e03,
|
|
1580
1580
|
],
|
|
1581
1581
|
[
|
|
1582
1582
|
1.988e02,
|
|
@@ -1584,10 +1584,10 @@ INC_OUTCOME_NON_MEDIA_FIXED = np.array([
|
|
|
1584
1584
|
5.180e01,
|
|
1585
1585
|
6.780e02,
|
|
1586
1586
|
1.979e02,
|
|
1587
|
-
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
-
|
|
1587
|
+
-2.085e05,
|
|
1588
|
+
1.539e04,
|
|
1589
|
+
9.246e03,
|
|
1590
|
+
-3.802e03,
|
|
1591
1591
|
],
|
|
1592
1592
|
[
|
|
1593
1593
|
1.991e02,
|
|
@@ -1595,10 +1595,10 @@ INC_OUTCOME_NON_MEDIA_FIXED = np.array([
|
|
|
1595
1595
|
5.174e01,
|
|
1596
1596
|
6.698e02,
|
|
1597
1597
|
1.972e02,
|
|
1598
|
-
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
-
|
|
1598
|
+
-1.870e05,
|
|
1599
|
+
1.525e04,
|
|
1600
|
+
9.182e03,
|
|
1601
|
+
-4.143e03,
|
|
1602
1602
|
],
|
|
1603
1603
|
[
|
|
1604
1604
|
1.986e02,
|
|
@@ -1606,10 +1606,10 @@ INC_OUTCOME_NON_MEDIA_FIXED = np.array([
|
|
|
1606
1606
|
5.175e01,
|
|
1607
1607
|
6.669e02,
|
|
1608
1608
|
1.948e02,
|
|
1609
|
-
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
-
|
|
1609
|
+
-1.902e05,
|
|
1610
|
+
1.520e04,
|
|
1611
|
+
9.380e03,
|
|
1612
|
+
-4.212e03,
|
|
1613
1613
|
],
|
|
1614
1614
|
[
|
|
1615
1615
|
1.986e02,
|
|
@@ -1617,10 +1617,10 @@ INC_OUTCOME_NON_MEDIA_FIXED = np.array([
|
|
|
1617
1617
|
5.178e01,
|
|
1618
1618
|
6.670e02,
|
|
1619
1619
|
1.965e02,
|
|
1620
|
-
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
-
|
|
1620
|
+
-1.952e05,
|
|
1621
|
+
1.512e04,
|
|
1622
|
+
9.660e03,
|
|
1623
|
+
-3.480e03,
|
|
1624
1624
|
],
|
|
1625
1625
|
],
|
|
1626
1626
|
[
|
|
@@ -1630,10 +1630,10 @@ INC_OUTCOME_NON_MEDIA_FIXED = np.array([
|
|
|
1630
1630
|
2.222e02,
|
|
1631
1631
|
2.692e02,
|
|
1632
1632
|
1.240e02,
|
|
1633
|
-
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
-
|
|
1633
|
+
-2.072e05,
|
|
1634
|
+
4.133e04,
|
|
1635
|
+
-2.037e04,
|
|
1636
|
+
-2.202e04,
|
|
1637
1637
|
],
|
|
1638
1638
|
[
|
|
1639
1639
|
1.432e03,
|
|
@@ -1641,10 +1641,10 @@ INC_OUTCOME_NON_MEDIA_FIXED = np.array([
|
|
|
1641
1641
|
2.231e02,
|
|
1642
1642
|
2.683e02,
|
|
1643
1643
|
1.240e02,
|
|
1644
|
-
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
-
|
|
1644
|
+
-2.105e05,
|
|
1645
|
+
4.117e04,
|
|
1646
|
+
-2.041e04,
|
|
1647
|
+
-2.168e04,
|
|
1648
1648
|
],
|
|
1649
1649
|
[
|
|
1650
1650
|
1.430e03,
|
|
@@ -1652,10 +1652,10 @@ INC_OUTCOME_NON_MEDIA_FIXED = np.array([
|
|
|
1652
1652
|
2.248e02,
|
|
1653
1653
|
2.675e02,
|
|
1654
1654
|
1.235e02,
|
|
1655
|
-
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
-
|
|
1655
|
+
-2.016e05,
|
|
1656
|
+
4.092e04,
|
|
1657
|
+
-2.030e04,
|
|
1658
|
+
-2.085e04,
|
|
1659
1659
|
],
|
|
1660
1660
|
[
|
|
1661
1661
|
1.436e03,
|
|
@@ -1663,10 +1663,10 @@ INC_OUTCOME_NON_MEDIA_FIXED = np.array([
|
|
|
1663
1663
|
2.231e02,
|
|
1664
1664
|
2.667e02,
|
|
1665
1665
|
1.244e02,
|
|
1666
|
-
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
-1.
|
|
1666
|
+
-1.972e05,
|
|
1667
|
+
4.087e04,
|
|
1668
|
+
-2.019e04,
|
|
1669
|
+
-1.921e04,
|
|
1670
1670
|
],
|
|
1671
1671
|
[
|
|
1672
1672
|
1.447e03,
|
|
@@ -1674,10 +1674,10 @@ INC_OUTCOME_NON_MEDIA_FIXED = np.array([
|
|
|
1674
1674
|
2.226e02,
|
|
1675
1675
|
2.650e02,
|
|
1676
1676
|
1.254e02,
|
|
1677
|
-
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
-1.
|
|
1677
|
+
-1.783e05,
|
|
1678
|
+
3.951e04,
|
|
1679
|
+
-1.994e04,
|
|
1680
|
+
-1.672e04,
|
|
1681
1681
|
],
|
|
1682
1682
|
[
|
|
1683
1683
|
1.430e03,
|
|
@@ -1685,10 +1685,10 @@ INC_OUTCOME_NON_MEDIA_FIXED = np.array([
|
|
|
1685
1685
|
2.232e02,
|
|
1686
1686
|
2.641e02,
|
|
1687
1687
|
1.254e02,
|
|
1688
|
-
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
-1.
|
|
1688
|
+
-1.802e05,
|
|
1689
|
+
4.147e04,
|
|
1690
|
+
-1.998e04,
|
|
1691
|
+
-1.571e04,
|
|
1692
1692
|
],
|
|
1693
1693
|
[
|
|
1694
1694
|
1.457e03,
|
|
@@ -1696,10 +1696,10 @@ INC_OUTCOME_NON_MEDIA_FIXED = np.array([
|
|
|
1696
1696
|
2.258e02,
|
|
1697
1697
|
2.621e02,
|
|
1698
1698
|
1.247e02,
|
|
1699
|
-
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
-
|
|
1699
|
+
-1.805e05,
|
|
1700
|
+
4.070e04,
|
|
1701
|
+
-2.015e04,
|
|
1702
|
+
-1.398e04,
|
|
1703
1703
|
],
|
|
1704
1704
|
[
|
|
1705
1705
|
1.456e03,
|
|
@@ -1707,10 +1707,10 @@ INC_OUTCOME_NON_MEDIA_FIXED = np.array([
|
|
|
1707
1707
|
2.276e02,
|
|
1708
1708
|
2.646e02,
|
|
1709
1709
|
1.246e02,
|
|
1710
|
-
-
|
|
1711
|
-
|
|
1712
|
-
2.
|
|
1713
|
-
-
|
|
1710
|
+
-1.764e05,
|
|
1711
|
+
4.198e04,
|
|
1712
|
+
-2.006e04,
|
|
1713
|
+
-1.299e04,
|
|
1714
1714
|
],
|
|
1715
1715
|
[
|
|
1716
1716
|
1.484e03,
|
|
@@ -1718,10 +1718,10 @@ INC_OUTCOME_NON_MEDIA_FIXED = np.array([
|
|
|
1718
1718
|
2.252e02,
|
|
1719
1719
|
2.668e02,
|
|
1720
1720
|
1.241e02,
|
|
1721
|
-
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
-
|
|
1721
|
+
-1.729e05,
|
|
1722
|
+
4.340e04,
|
|
1723
|
+
-1.995e04,
|
|
1724
|
+
-1.352e04,
|
|
1725
1725
|
],
|
|
1726
1726
|
[
|
|
1727
1727
|
1.475e03,
|
|
@@ -1729,10 +1729,10 @@ INC_OUTCOME_NON_MEDIA_FIXED = np.array([
|
|
|
1729
1729
|
2.238e02,
|
|
1730
1730
|
2.669e02,
|
|
1731
1731
|
1.244e02,
|
|
1732
|
-
-
|
|
1733
|
-
|
|
1734
|
-
2.
|
|
1735
|
-
-
|
|
1732
|
+
-1.735e05,
|
|
1733
|
+
4.349e04,
|
|
1734
|
+
-2.014e04,
|
|
1735
|
+
-1.338e04,
|
|
1736
1736
|
],
|
|
1737
1737
|
],
|
|
1738
1738
|
])
|
meridian/analysis/visualizer.py
CHANGED
|
@@ -465,14 +465,14 @@ class ModelFit:
|
|
|
465
465
|
else:
|
|
466
466
|
y_axis_label = summary_text.KPI_LABEL
|
|
467
467
|
plot = (
|
|
468
|
-
alt.Chart(model_fit_df, width=c.
|
|
468
|
+
alt.Chart(model_fit_df, width=c.VEGALITE_FACET_EXTRA_LARGE_WIDTH)
|
|
469
469
|
.mark_line()
|
|
470
470
|
.encode(
|
|
471
471
|
x=alt.X(
|
|
472
472
|
f'{c.TIME}:T',
|
|
473
473
|
title='Time period',
|
|
474
474
|
axis=alt.Axis(
|
|
475
|
-
format=
|
|
475
|
+
format=c.QUARTER_FORMAT,
|
|
476
476
|
grid=False,
|
|
477
477
|
tickCount=8,
|
|
478
478
|
domainColor=c.GREY_300,
|
|
@@ -1405,7 +1405,7 @@ class MediaSummary:
|
|
|
1405
1405
|
confidence_level: float = c.DEFAULT_CONFIDENCE_LEVEL,
|
|
1406
1406
|
selected_times: Sequence[str] | None = None,
|
|
1407
1407
|
marginal_roi_by_reach: bool = True,
|
|
1408
|
-
non_media_baseline_values: Sequence[
|
|
1408
|
+
non_media_baseline_values: Sequence[float] | None = None,
|
|
1409
1409
|
):
|
|
1410
1410
|
"""Initializes the media summary metrics based on the model data and params.
|
|
1411
1411
|
|
|
@@ -1420,13 +1420,11 @@ class MediaSummary:
|
|
|
1420
1420
|
next dollar spent only impacts reach, holding frequency constant. If
|
|
1421
1421
|
this argument is False, we assume the next dollar spent only impacts
|
|
1422
1422
|
frequency, holding reach constant.
|
|
1423
|
-
non_media_baseline_values: Optional list of shape
|
|
1424
|
-
Each element is
|
|
1425
|
-
used as baseline for the given channel
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
None, the minimum value is used as baseline for each non_media treatment
|
|
1429
|
-
channel.
|
|
1423
|
+
non_media_baseline_values: Optional list of shape
|
|
1424
|
+
`(n_non_media_channels,)`. Each element is a float denoting the fixed
|
|
1425
|
+
value which will be used as baseline for the given channel. If `None`,
|
|
1426
|
+
the values defined with `ModelSpec.non_media_baseline_values`
|
|
1427
|
+
will be used.
|
|
1430
1428
|
"""
|
|
1431
1429
|
self._meridian = meridian
|
|
1432
1430
|
self._analyzer = analyzer.Analyzer(meridian)
|
|
@@ -1629,7 +1627,7 @@ class MediaSummary:
|
|
|
1629
1627
|
confidence_level: float | None = None,
|
|
1630
1628
|
selected_times: Sequence[str] | None = None,
|
|
1631
1629
|
marginal_roi_by_reach: bool = True,
|
|
1632
|
-
non_media_baseline_values: Sequence[
|
|
1630
|
+
non_media_baseline_values: Sequence[float] | None = None,
|
|
1633
1631
|
):
|
|
1634
1632
|
"""Runs the computation for the media summary metrics with new parameters.
|
|
1635
1633
|
|
|
@@ -1644,31 +1642,47 @@ class MediaSummary:
|
|
|
1644
1642
|
dollar spent only impacts reach, holding frequency constant. If `False`,
|
|
1645
1643
|
the assumption is the next dollar spent only impacts frequency, holding
|
|
1646
1644
|
reach constant.
|
|
1647
|
-
non_media_baseline_values: Optional list of shape
|
|
1648
|
-
Each element is
|
|
1649
|
-
used as baseline for the given channel
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
None, the minimum value is used as baseline for each non_media treatment
|
|
1653
|
-
channel.
|
|
1645
|
+
non_media_baseline_values: Optional list of shape
|
|
1646
|
+
`(n_non_media_channels,)`. Each element is a float denoting the fixed
|
|
1647
|
+
value which will be used as baseline for the given channel. If `None`,
|
|
1648
|
+
the values defined with `ModelSpec.non_media_baseline_values`
|
|
1649
|
+
will be used.
|
|
1654
1650
|
"""
|
|
1655
1651
|
self._confidence_level = confidence_level or self._confidence_level
|
|
1656
1652
|
self._selected_times = selected_times
|
|
1657
1653
|
self._marginal_roi_by_reach = marginal_roi_by_reach
|
|
1658
1654
|
self._non_media_baseline_values = non_media_baseline_values
|
|
1659
1655
|
|
|
1660
|
-
def plot_channel_contribution_area_chart(
|
|
1656
|
+
def plot_channel_contribution_area_chart(
|
|
1657
|
+
self, time_granularity: str = c.QUARTERLY
|
|
1658
|
+
) -> alt.Chart:
|
|
1661
1659
|
"""Plots a stacked area chart of the contribution share per channel by time.
|
|
1662
1660
|
|
|
1661
|
+
Args:
|
|
1662
|
+
time_granularity: The granularity for the time axis. Options are `weekly`
|
|
1663
|
+
or `quarterly`. Defaults to `quarterly`.
|
|
1664
|
+
|
|
1663
1665
|
Returns:
|
|
1664
1666
|
An Altair plot showing the contribution share per channel by time.
|
|
1667
|
+
|
|
1668
|
+
Raises:
|
|
1669
|
+
ValueError: If time_granularity is not one of the allowed constants.
|
|
1665
1670
|
"""
|
|
1671
|
+
if time_granularity not in c.TIME_GRANULARITIES:
|
|
1672
|
+
raise ValueError(
|
|
1673
|
+
f'time_granularity must be one of {c.TIME_GRANULARITIES}'
|
|
1674
|
+
)
|
|
1675
|
+
|
|
1676
|
+
x_axis_format = (
|
|
1677
|
+
c.DATE_FORMAT if time_granularity == c.WEEKLY else c.QUARTER_FORMAT
|
|
1678
|
+
)
|
|
1679
|
+
|
|
1666
1680
|
outcome_df = self._transform_contribution_metrics(
|
|
1667
1681
|
include_non_paid=True, aggregate_times=False
|
|
1668
1682
|
)
|
|
1669
1683
|
|
|
1670
1684
|
# Ensure proper ordering for the stacked area chart. Baseline should be at
|
|
1671
|
-
# the bottom.
|
|
1685
|
+
# the bottom. Separate the *stacking* order from the *legend* order.
|
|
1672
1686
|
stack_order = sorted([
|
|
1673
1687
|
channel
|
|
1674
1688
|
for channel in outcome_df[c.CHANNEL].unique()
|
|
@@ -1691,7 +1705,7 @@ class MediaSummary:
|
|
|
1691
1705
|
)
|
|
1692
1706
|
|
|
1693
1707
|
plot = (
|
|
1694
|
-
alt.Chart(outcome_df, width=c.
|
|
1708
|
+
alt.Chart(outcome_df, width=c.VEGALITE_FACET_EXTRA_LARGE_WIDTH)
|
|
1695
1709
|
.mark_area()
|
|
1696
1710
|
.transform_calculate(
|
|
1697
1711
|
sort_channel=f'indexof({stack_order}, datum.channel)'
|
|
@@ -1701,7 +1715,7 @@ class MediaSummary:
|
|
|
1701
1715
|
f'{c.TIME}:T',
|
|
1702
1716
|
title='Time period',
|
|
1703
1717
|
axis=alt.Axis(
|
|
1704
|
-
format=
|
|
1718
|
+
format=x_axis_format,
|
|
1705
1719
|
grid=False,
|
|
1706
1720
|
tickCount=8,
|
|
1707
1721
|
domainColor=c.GREY_300,
|
|
@@ -1730,12 +1744,13 @@ class MediaSummary:
|
|
|
1730
1744
|
labelFontSize=c.AXIS_FONT_SIZE,
|
|
1731
1745
|
labelFont=c.FONT_ROBOTO,
|
|
1732
1746
|
title=None,
|
|
1747
|
+
orient='bottom',
|
|
1733
1748
|
),
|
|
1734
1749
|
scale=alt.Scale(domain=legend_order),
|
|
1735
1750
|
sort=legend_order,
|
|
1736
1751
|
),
|
|
1737
1752
|
tooltip=[
|
|
1738
|
-
alt.Tooltip(f'{c.TIME}:T', format=
|
|
1753
|
+
alt.Tooltip(f'{c.TIME}:T', format=c.DATE_FORMAT),
|
|
1739
1754
|
c.CHANNEL,
|
|
1740
1755
|
alt.Tooltip(f'{c.INCREMENTAL_OUTCOME}:Q', format=',.2f'),
|
|
1741
1756
|
],
|
|
@@ -1751,16 +1766,31 @@ class MediaSummary:
|
|
|
1751
1766
|
)
|
|
1752
1767
|
return plot
|
|
1753
1768
|
|
|
1754
|
-
def plot_channel_contribution_bump_chart(
|
|
1755
|
-
|
|
1769
|
+
def plot_channel_contribution_bump_chart(
|
|
1770
|
+
self, time_granularity: str = c.QUARTERLY
|
|
1771
|
+
) -> alt.Chart:
|
|
1772
|
+
"""Plots a bump chart of channel contribution rank over time.
|
|
1756
1773
|
|
|
1757
1774
|
This chart shows the relative rank of each channel's contribution,
|
|
1758
|
-
including the baseline, based on incremental outcome
|
|
1775
|
+
including the baseline, based on incremental outcome. Depending on the
|
|
1776
|
+
time_granularity, ranks are shown either weekly or at the end of each
|
|
1759
1777
|
quarter. Rank 1 represents the highest contribution.
|
|
1760
1778
|
|
|
1779
|
+
Args:
|
|
1780
|
+
time_granularity: The granularity for the time axis. Options are `weekly`
|
|
1781
|
+
or `quarterly`. Defaults to `quarterly`.
|
|
1782
|
+
|
|
1761
1783
|
Returns:
|
|
1762
|
-
An Altair plot showing the contribution rank per channel by
|
|
1784
|
+
An Altair plot showing the contribution rank per channel by time.
|
|
1785
|
+
|
|
1786
|
+
Raises:
|
|
1787
|
+
ValueError: If time_granularity is not one of the allowed constants.
|
|
1763
1788
|
"""
|
|
1789
|
+
if time_granularity not in c.TIME_GRANULARITIES:
|
|
1790
|
+
raise ValueError(
|
|
1791
|
+
f'time_granularity must be one of {c.TIME_GRANULARITIES}'
|
|
1792
|
+
)
|
|
1793
|
+
|
|
1764
1794
|
outcome_df = self._transform_contribution_metrics(
|
|
1765
1795
|
include_non_paid=True, aggregate_times=False
|
|
1766
1796
|
)
|
|
@@ -1770,30 +1800,37 @@ class MediaSummary:
|
|
|
1770
1800
|
method='first', ascending=False
|
|
1771
1801
|
)
|
|
1772
1802
|
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1803
|
+
if time_granularity == c.QUARTERLY:
|
|
1804
|
+
# Filter data to keep only the last available date within each quarter
|
|
1805
|
+
# for a quarterly view of ranking changes.
|
|
1806
|
+
unique_times = pd.Series(outcome_df[c.TIME].unique()).sort_values()
|
|
1807
|
+
quarters = unique_times.dt.to_period('Q')
|
|
1808
|
+
quarterly_dates = unique_times[~quarters.duplicated(keep='last')]
|
|
1809
|
+
plot_df = outcome_df[outcome_df[c.TIME].isin(quarterly_dates)].copy()
|
|
1810
|
+
x_axis_format = c.QUARTER_FORMAT
|
|
1811
|
+
tooltip_time_format = c.QUARTER_FORMAT
|
|
1812
|
+
tooltip_time_title = 'Quarter'
|
|
1813
|
+
else:
|
|
1814
|
+
plot_df = outcome_df.copy()
|
|
1815
|
+
x_axis_format = c.DATE_FORMAT
|
|
1816
|
+
tooltip_time_format = c.DATE_FORMAT
|
|
1817
|
+
tooltip_time_title = 'Week'
|
|
1781
1818
|
|
|
1782
1819
|
legend_order = [c.BASELINE] + sorted([
|
|
1783
1820
|
channel
|
|
1784
|
-
for channel in
|
|
1821
|
+
for channel in plot_df[c.CHANNEL].unique()
|
|
1785
1822
|
if channel != c.BASELINE
|
|
1786
1823
|
])
|
|
1787
1824
|
|
|
1788
1825
|
plot = (
|
|
1789
|
-
alt.Chart(
|
|
1826
|
+
alt.Chart(plot_df, width=c.VEGALITE_FACET_EXTRA_LARGE_WIDTH)
|
|
1790
1827
|
.mark_line(point=True)
|
|
1791
1828
|
.encode(
|
|
1792
1829
|
x=alt.X(
|
|
1793
1830
|
f'{c.TIME}:T',
|
|
1794
1831
|
title='Time period',
|
|
1795
1832
|
axis=alt.Axis(
|
|
1796
|
-
format=
|
|
1833
|
+
format=x_axis_format,
|
|
1797
1834
|
grid=False,
|
|
1798
1835
|
domainColor=c.GREY_300,
|
|
1799
1836
|
),
|
|
@@ -1819,12 +1856,17 @@ class MediaSummary:
|
|
|
1819
1856
|
labelFontSize=c.AXIS_FONT_SIZE,
|
|
1820
1857
|
labelFont=c.FONT_ROBOTO,
|
|
1821
1858
|
title=None,
|
|
1859
|
+
orient='bottom',
|
|
1822
1860
|
),
|
|
1823
1861
|
scale=alt.Scale(domain=legend_order),
|
|
1824
1862
|
sort=legend_order,
|
|
1825
1863
|
),
|
|
1826
1864
|
tooltip=[
|
|
1827
|
-
alt.Tooltip(
|
|
1865
|
+
alt.Tooltip(
|
|
1866
|
+
f'{c.TIME}:T',
|
|
1867
|
+
format=tooltip_time_format,
|
|
1868
|
+
title=tooltip_time_title,
|
|
1869
|
+
),
|
|
1828
1870
|
alt.Tooltip(f'{c.CHANNEL}:N', title='Channel'),
|
|
1829
1871
|
alt.Tooltip('rank:O', title='Rank'),
|
|
1830
1872
|
alt.Tooltip(
|