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.
@@ -75,8 +75,8 @@ class Summarizer:
75
75
  self,
76
76
  filename: str,
77
77
  filepath: str,
78
- start_date: tc.Date | None = None,
79
- end_date: tc.Date | None = None,
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 | None = None,
97
- end_date: tc.Date | None = None,
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(template_env, media_summary),
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().to_json(),
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().to_json(),
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,
@@ -1515,13 +1515,13 @@ INC_OUTCOME_NON_MEDIA_FIXED = np.array([
1515
1515
  [
1516
1516
  1.991e02,
1517
1517
  5.116e02,
1518
- 5.190e01,
1518
+ 5.191e01,
1519
1519
  6.623e02,
1520
1520
  1.969e02,
1521
- -7.194e05,
1522
- -1.927e04,
1523
- -1.809e03,
1524
- -3.006e04,
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
- -7.001e05,
1533
- -1.915e04,
1534
- -1.729e03,
1535
- -2.933e04,
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
- -7.005e05,
1544
- -1.901e04,
1545
- -1.737e03,
1546
- -3.111e04,
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
- -6.618e05,
1555
- -1.831e04,
1556
- -1.688e03,
1557
- -2.833e04,
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
- -6.748e05,
1566
- -1.849e04,
1567
- -1.660e03,
1568
- -3.077e04,
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
- -6.882e05,
1577
- -1.859e04,
1578
- -1.622e03,
1579
- -2.499e04,
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
- -6.667e05,
1588
- -1.809e04,
1589
- -1.600e03,
1590
- -2.555e04,
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
- -5.978e05,
1599
- -1.809e04,
1600
- -1.589e03,
1601
- -2.782e04,
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
- -6.080e05,
1610
- -1.806e04,
1611
- -1.619e03,
1612
- -2.830e04,
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
- -6.241e05,
1621
- -1.795e04,
1622
- -1.663e03,
1623
- -2.348e04,
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
- -6.622e05,
1634
- -3.899e04,
1635
- 3.034e03,
1636
- -1.467e05,
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
- -6.727e05,
1645
- -3.886e04,
1646
- 3.040e03,
1647
- -1.444e05,
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
- -6.444e05,
1656
- -3.858e04,
1657
- 3.023e03,
1658
- -1.387e05,
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
- -6.301e05,
1667
- -3.852e04,
1668
- 3.006e03,
1669
- -1.275e05,
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
- -5.698e05,
1678
- -3.717e04,
1679
- 2.969e03,
1680
- -1.106e05,
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
- -5.759e05,
1689
- -3.911e04,
1690
- 2.974e03,
1691
- -1.045e05,
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
- -5.770e05,
1700
- -3.842e04,
1701
- 3.002e03,
1702
- -9.320e04,
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
- -5.637e05,
1711
- -3.962e04,
1712
- 2.988e03,
1713
- -8.621e04,
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
- -5.527e05,
1722
- -4.099e04,
1723
- 2.969e03,
1724
- -8.990e04,
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
- -5.546e05,
1733
- -4.105e04,
1734
- 2.999e03,
1735
- -8.896e04,
1732
+ -1.735e05,
1733
+ 4.349e04,
1734
+ -2.014e04,
1735
+ -1.338e04,
1736
1736
  ],
1737
1737
  ],
1738
1738
  ])
@@ -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.VEGALITE_FACET_DEFAULT_WIDTH)
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='%Y %b',
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[str | float] | None = None,
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 (n_non_media_channels,).
1424
- Each element is either a float (which means that the fixed value will be
1425
- used as baseline for the given channel) or one of the strings "min" or
1426
- "max" (which mean that the global minimum or maximum value will be used
1427
- as baseline for the values of the given non_media treatment channel). If
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[str | float] | None = None,
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 (n_non_media_channels,).
1648
- Each element is either a float (which means that the fixed value will be
1649
- used as baseline for the given channel) or one of the strings "min" or
1650
- "max" (which mean that the global minimum or maximum value will be used
1651
- as baseline for the values of the given non_media treatment channel). If
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(self) -> alt.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. Separate the *stacking* order from the *legend* order.
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.VEGALITE_FACET_LARGE_WIDTH)
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='%Y Q%q',
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='%Y-%m-%d'),
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(self) -> alt.Chart:
1755
- """Plots a bump chart of channel contribution rank over time (Quarterly).
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 at the end of each
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 quarter.
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
- # Filter data to keep only the last available date within each quarter
1774
- # for a quarterly view of ranking changes.
1775
- unique_times = pd.Series(outcome_df[c.TIME].unique()).sort_values()
1776
- quarters = unique_times.dt.to_period('Q')
1777
- quarterly_dates = unique_times[~quarters.duplicated(keep='last')]
1778
- quarterly_rank_df = outcome_df[
1779
- outcome_df[c.TIME].isin(quarterly_dates)
1780
- ].copy()
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 quarterly_rank_df[c.CHANNEL].unique()
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(quarterly_rank_df, width=c.VEGALITE_FACET_DEFAULT_WIDTH)
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='%Y Q%q',
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(f'{c.TIME}:T', format='%Y Q%q', title='Quarter'),
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(