google-meridian 1.3.1__py3-none-any.whl → 1.4.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.
Files changed (74) hide show
  1. {google_meridian-1.3.1.dist-info → google_meridian-1.4.0.dist-info}/METADATA +13 -9
  2. google_meridian-1.4.0.dist-info/RECORD +108 -0
  3. {google_meridian-1.3.1.dist-info → google_meridian-1.4.0.dist-info}/top_level.txt +1 -0
  4. meridian/analysis/__init__.py +1 -2
  5. meridian/analysis/analyzer.py +0 -1
  6. meridian/analysis/optimizer.py +5 -3
  7. meridian/analysis/review/checks.py +81 -30
  8. meridian/analysis/review/constants.py +4 -0
  9. meridian/analysis/review/results.py +40 -9
  10. meridian/analysis/summarizer.py +8 -3
  11. meridian/analysis/test_utils.py +934 -485
  12. meridian/analysis/visualizer.py +11 -7
  13. meridian/backend/__init__.py +53 -5
  14. meridian/backend/test_utils.py +72 -0
  15. meridian/constants.py +2 -0
  16. meridian/data/load.py +2 -0
  17. meridian/data/test_utils.py +82 -10
  18. meridian/model/__init__.py +2 -0
  19. meridian/model/context.py +925 -0
  20. meridian/model/eda/__init__.py +0 -1
  21. meridian/model/eda/constants.py +13 -2
  22. meridian/model/eda/eda_engine.py +299 -37
  23. meridian/model/eda/eda_outcome.py +21 -1
  24. meridian/model/equations.py +418 -0
  25. meridian/model/knots.py +75 -47
  26. meridian/model/model.py +93 -792
  27. meridian/{analysis/templates → templates}/card.html.jinja +1 -1
  28. meridian/{analysis/templates → templates}/chart.html.jinja +1 -1
  29. meridian/{analysis/templates → templates}/chips.html.jinja +1 -1
  30. meridian/{analysis → templates}/formatter.py +12 -1
  31. meridian/templates/formatter_test.py +216 -0
  32. meridian/{analysis/templates → templates}/insights.html.jinja +1 -1
  33. meridian/{analysis/templates → templates}/stats.html.jinja +1 -1
  34. meridian/{analysis/templates → templates}/style.css +1 -1
  35. meridian/{analysis/templates → templates}/style.scss +1 -1
  36. meridian/{analysis/templates → templates}/summary.html.jinja +4 -2
  37. meridian/{analysis/templates → templates}/table.html.jinja +1 -1
  38. meridian/version.py +1 -1
  39. scenarioplanner/__init__.py +42 -0
  40. scenarioplanner/converters/__init__.py +25 -0
  41. scenarioplanner/converters/dataframe/__init__.py +28 -0
  42. scenarioplanner/converters/dataframe/budget_opt_converters.py +383 -0
  43. scenarioplanner/converters/dataframe/common.py +71 -0
  44. scenarioplanner/converters/dataframe/constants.py +137 -0
  45. scenarioplanner/converters/dataframe/converter.py +42 -0
  46. scenarioplanner/converters/dataframe/dataframe_model_converter.py +70 -0
  47. scenarioplanner/converters/dataframe/marketing_analyses_converters.py +543 -0
  48. scenarioplanner/converters/dataframe/rf_opt_converters.py +314 -0
  49. scenarioplanner/converters/mmm.py +743 -0
  50. scenarioplanner/converters/mmm_converter.py +58 -0
  51. scenarioplanner/converters/sheets.py +156 -0
  52. scenarioplanner/converters/test_data.py +714 -0
  53. scenarioplanner/linkingapi/__init__.py +47 -0
  54. scenarioplanner/linkingapi/constants.py +27 -0
  55. scenarioplanner/linkingapi/url_generator.py +131 -0
  56. scenarioplanner/mmm_ui_proto_generator.py +354 -0
  57. schema/__init__.py +15 -0
  58. schema/mmm_proto_generator.py +71 -0
  59. schema/model_consumer.py +133 -0
  60. schema/processors/__init__.py +77 -0
  61. schema/processors/budget_optimization_processor.py +832 -0
  62. schema/processors/common.py +64 -0
  63. schema/processors/marketing_processor.py +1136 -0
  64. schema/processors/model_fit_processor.py +367 -0
  65. schema/processors/model_kernel_processor.py +117 -0
  66. schema/processors/model_processor.py +412 -0
  67. schema/processors/reach_frequency_optimization_processor.py +584 -0
  68. schema/test_data.py +380 -0
  69. schema/utils/__init__.py +1 -0
  70. schema/utils/date_range_bucketing.py +117 -0
  71. google_meridian-1.3.1.dist-info/RECORD +0 -76
  72. meridian/model/eda/meridian_eda.py +0 -220
  73. {google_meridian-1.3.1.dist-info → google_meridian-1.4.0.dist-info}/WHEEL +0 -0
  74. {google_meridian-1.3.1.dist-info → google_meridian-1.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: google-meridian
3
- Version: 1.3.1
3
+ Version: 1.4.0
4
4
  Summary: Google's open source mixed marketing model library, helps you understand your return on investment and direct your ad spend with confidence.
5
5
  Author-email: The Meridian Authors <no-reply@google.com>
6
6
  Project-URL: homepage, https://github.com/google/meridian
@@ -22,12 +22,11 @@ Requires-Dist: joblib
22
22
  Requires-Dist: natsort<8,>=7.1.1
23
23
  Requires-Dist: numpy<3,>=2.0.2
24
24
  Requires-Dist: pandas<3,>=2.2.2
25
- Requires-Dist: patsy<1,>=0.5.3
26
25
  Requires-Dist: scipy<2,>=1.13.1
27
26
  Requires-Dist: statsmodels>=0.14.5
28
- Requires-Dist: tensorflow<2.19,>=2.18
27
+ Requires-Dist: tensorflow<2.21,>=2.18
29
28
  Requires-Dist: tensorflow-probability<0.26,>=0.25
30
- Requires-Dist: tf-keras<2.19,>=2.18
29
+ Requires-Dist: tf-keras<2.21,>=2.18
31
30
  Requires-Dist: xarray
32
31
  Provides-Extra: dev
33
32
  Requires-Dist: pytest>=8.0.0; extra == "dev"
@@ -38,16 +37,21 @@ Provides-Extra: colab
38
37
  Requires-Dist: psutil; extra == "colab"
39
38
  Requires-Dist: python-calamine; extra == "colab"
40
39
  Provides-Extra: and-cuda
41
- Requires-Dist: tensorflow[and-cuda]<2.19,>=2.18; extra == "and-cuda"
40
+ Requires-Dist: tensorflow[and-cuda]<2.21,>=2.18; extra == "and-cuda"
42
41
  Provides-Extra: mlflow
43
42
  Requires-Dist: mlflow; extra == "mlflow"
44
43
  Provides-Extra: jax
45
- Requires-Dist: jax==0.4.26; extra == "jax"
46
- Requires-Dist: jaxlib==0.4.26; extra == "jax"
44
+ Requires-Dist: jax==0.5.3; extra == "jax"
45
+ Requires-Dist: jaxlib==0.5.3; extra == "jax"
47
46
  Requires-Dist: tensorflow-probability[substrates-jax]==0.25.0; extra == "jax"
48
47
  Provides-Extra: schema
49
- Requires-Dist: mmm-proto-schema; extra == "schema"
48
+ Requires-Dist: mmm-proto-schema>=1.1.0; extra == "schema"
50
49
  Requires-Dist: semver; extra == "schema"
50
+ Provides-Extra: scenarioplanner
51
+ Requires-Dist: google-api-python-client; extra == "scenarioplanner"
52
+ Requires-Dist: google-auth; extra == "scenarioplanner"
53
+ Requires-Dist: mmm-proto-schema>=1.1.0; extra == "scenarioplanner"
54
+ Requires-Dist: semver; extra == "scenarioplanner"
51
55
  Dynamic: license-file
52
56
 
53
57
  # About Meridian
@@ -203,7 +207,7 @@ To cite this repository:
203
207
  author = {Google Meridian Marketing Mix Modeling Team},
204
208
  title = {Meridian: Marketing Mix Modeling},
205
209
  url = {https://github.com/google/meridian},
206
- version = {1.3.1},
210
+ version = {1.4.0},
207
211
  year = {2025},
208
212
  }
209
213
  ```
@@ -0,0 +1,108 @@
1
+ google_meridian-1.4.0.dist-info/licenses/LICENSE,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
2
+ meridian/__init__.py,sha256=0fOT5oNZF7-pbiWWGUefV-ysafttieG079m1ijMFQO8,861
3
+ meridian/constants.py,sha256=JEVut9ZvnHVtdCSU7KGeR1TpR_vFXAOlE9d7leVXeHk,20348
4
+ meridian/version.py,sha256=5Udd9dsBQAuAbv6cGREFYqdM8M7pK4WJU3PPEHNUgqo,644
5
+ meridian/analysis/__init__.py,sha256=AM7xpqoeC-mmY4tPIyHisjQ2MICI7v3jSri--DhDqXA,874
6
+ meridian/analysis/analyzer.py,sha256=qGKYoEpWC6pDf2sDrwLhpa2putDnpUzbBpuNqL1lGi4,219910
7
+ meridian/analysis/optimizer.py,sha256=6OqJg8T09g4GqJc6_DnYDJsBAlY4ToUb3MRtS0EH4Nc,126316
8
+ meridian/analysis/summarizer.py,sha256=-yemGGctDs23FnhaLWAwfsQw2DqoyB3eljqt0Q8DyEw,19263
9
+ meridian/analysis/summary_text.py,sha256=I_smDkZJYp2j77ea-9AIbgeraDa7-qUYyb-IthP2qO4,12438
10
+ meridian/analysis/test_utils.py,sha256=wSdFHzLbNLdv_InRreyKD_lC7Cvdv-upQOWf8dqCAPg,88045
11
+ meridian/analysis/visualizer.py,sha256=ljgN5ZCG_3aXq4Js0sE9sQd11TS1gi9KvPKMJ7_kW_A,94345
12
+ meridian/analysis/review/__init__.py,sha256=cF24EbhiVSs-tvtRf59uVin39tu6aCTTCaeEdv6ISZ8,804
13
+ meridian/analysis/review/checks.py,sha256=QEUVwC8L9Pif3y0B_OVAUpZeN6EnFGKH2oXA_dsdgbc,26893
14
+ meridian/analysis/review/configs.py,sha256=5JJ8v6n22GNBmE78xNX6jwdjkZz2qar4Q9YTcVqzcoI,3653
15
+ meridian/analysis/review/constants.py,sha256=9tnnc_vAaJi9mnZ0GrRr86RsVq5fyhBaEtIvOLNmn8A,1498
16
+ meridian/analysis/review/results.py,sha256=ZZiAdFrqySzcbjrCEacBLQS_ddiklZ34yD1BWns5SYI,17418
17
+ meridian/analysis/review/reviewer.py,sha256=BcfmqHjp-30iZBlrzWfXDN1IJU-UIjINxZ7lsrj5Mts,6675
18
+ meridian/backend/__init__.py,sha256=i2XOZnIPo3xAKpMGT39-4CVnVdf4cJyO0jB9FEE4zlQ,41337
19
+ meridian/backend/config.py,sha256=B9VQnhBfg9RW04GNbt7F5uCugByenoJzt-keFLLYEp8,3561
20
+ meridian/backend/test_utils.py,sha256=oJNosF_x_BzNuia8LzLFb_YfjGWHRCzR5FXNN5KQ8sw,13738
21
+ meridian/data/__init__.py,sha256=StIe-wfYnnbfUbKtZHwnAQcRQUS8XCZk_PCaEzw90Ww,929
22
+ meridian/data/arg_builder.py,sha256=Kqlt88bOqFj6D3xNwvWo4MBwNwcDFHzd-wMfEOmLoPU,3741
23
+ meridian/data/data_frame_input_data_builder.py,sha256=_hexZMFAuAowgo6FaOGElHSFHqhGnHQwEEBcwnT3zUE,27295
24
+ meridian/data/input_data.py,sha256=Qlxm4El6h1SRPsWDqZoKkOcMtrjiRWr3z8sU2mtghRA,43151
25
+ meridian/data/input_data_builder.py,sha256=tbZjVXPDfmtndVyJA0fmzGzZwZb0RCEjXOTXb-ga8Nc,25648
26
+ meridian/data/load.py,sha256=ETX8Z62Gk6JcxFxvyB4XQhpNcRSqBRIO4_sTAN58mCY,40172
27
+ meridian/data/nd_array_input_data_builder.py,sha256=lfpmnENGuSGKyUd7bDGAwoLqHqteOKmHdKl0VI2wCQA,16341
28
+ meridian/data/test_utils.py,sha256=4BEHM0V78AkbrUIYWJLzalUgC-zkc0VeNiXrqzr2iXw,62435
29
+ meridian/data/time_coordinates.py,sha256=C5A5fscSLjPH6G9YT8OspgIlCrkMY7y8dMFEt3tNSnE,9874
30
+ meridian/mlflow/__init__.py,sha256=elwXUqPQYi7VF9PYjelU1tydfcUrmtuoq6eJCOnV9bk,693
31
+ meridian/mlflow/autolog.py,sha256=SZsrynLjozcyrAFCNWiqchSa2yOszVnwFBGz23BmWUU,6379
32
+ meridian/model/__init__.py,sha256=NCmuqkzIsGTy0xKgr9XFmjq5JV1Uzalj7PlcvWL9Bvw,1085
33
+ meridian/model/adstock_hill.py,sha256=HoRKjyL03pCTBz6Utof9wEvlQCFM43BvrEW_oupj7NU,17688
34
+ meridian/model/context.py,sha256=W_j3LnmpAx7Cs80MeqZGe8XKpoqwy6PfGIe8U7OUuTY,34622
35
+ meridian/model/equations.py,sha256=bQnFU5YdskwxBVJlLO4AX3zV8VXerb7lNhMedK0Cwcc,17174
36
+ meridian/model/knots.py,sha256=XTii81yrX1abu0llLvrJquRadEqFaUDIlYpvR5OBP1o,27661
37
+ meridian/model/media.py,sha256=skjy4Vd8LfDQWlqR_2lJ1qbG9UcS1dow5W45BAu4qk8,14599
38
+ meridian/model/model.py,sha256=9-cHk9kno3G79t9z0rstcAk_QalKWr6QA75OtJqjpSY,41216
39
+ meridian/model/model_test_data.py,sha256=XGBz8RGdCsjAUOmgxX3CfWSj-_hdq2Lc8saFCqmImwM,23901
40
+ meridian/model/posterior_sampler.py,sha256=f3MayglIgBeBjWeXJU_RgT9cCugcjJ3aEjHqaWPsTbg,26806
41
+ meridian/model/prior_distribution.py,sha256=ZArW4uXIPPQL6hRWiGZUzcHktbkjE_vOklvlbp9LR64,57662
42
+ meridian/model/prior_sampler.py,sha256=iLvCefhA4WY0ENcnLK9471WUZPPyzQ1je58MRjxKv74,25460
43
+ meridian/model/spec.py,sha256=VlK6WJiPo2lzOF0O2judtJ6O3uEw7wYL5AT8bioq4gE,19188
44
+ meridian/model/transformers.py,sha256=HxlVJitxP-wu-NOHU0tArFUZ4NAO3c7adAYj4Zvqnvo,8363
45
+ meridian/model/eda/__init__.py,sha256=bMj9kd2LWU_LQZAjQv54FFggzdv4CKRYblvc-0cHXc4,768
46
+ meridian/model/eda/constants.py,sha256=AMq2G-8y7OPLfMismDDIDKazyuG8Bd3W31NpwQ9oIXM,1047
47
+ meridian/model/eda/eda_engine.py,sha256=AxvKxdH8Q_TWlGZ58bWcfGMSgxkHZ2wrHloPuzS5C4Y,73324
48
+ meridian/model/eda/eda_outcome.py,sha256=cR9M49e6bDrBNxHOThW3aQlX5gZCOENC7GBljKQx7OY,6475
49
+ meridian/model/eda/eda_spec.py,sha256=diieYyZH0ee3ZLy0rGFMcWrrgiUrz2HctMwOrmtJR6w,2871
50
+ meridian/templates/card.html.jinja,sha256=gaQSHh8xN-LqHzI-xsHaORsaeOD7hnxkq-Fcw-ytZ8M,1284
51
+ meridian/templates/chart.html.jinja,sha256=j8rNLOfXxKx4f5TpP9_W8uCM8zKys8ga75jv-XCny2g,1086
52
+ meridian/templates/chips.html.jinja,sha256=IhPmAI7qZkXj5NeLUKVMCSKFzwv9yR8Qq140t36U10A,1030
53
+ meridian/templates/formatter.py,sha256=75KtZA2y6iyxpkVM3CLmpUtJ1-umBP9xXxdXkkSZcr8,7547
54
+ meridian/templates/formatter_test.py,sha256=nlefFTQaSArNyZWzGUuzCKtt3P3PJNZFmkGm0XMYwBY,8543
55
+ meridian/templates/insights.html.jinja,sha256=IRm49X49HZX-rc6z0uZmpsA_1JLD83UhI2BacOanHLY,606
56
+ meridian/templates/stats.html.jinja,sha256=JMRC_W2PyReU30ISOd8uv2ugDpYmSU3XtPk2OTsaQus,772
57
+ meridian/templates/style.css,sha256=O7YCKVqXgK4Ms3nnGmCVW4hILn4GCBRe4e15XJaP4ww,5492
58
+ meridian/templates/style.scss,sha256=he5jXpGatNfI6vtNbqdENapLiwIlXjqIBkKwxNVEvyw,5076
59
+ meridian/templates/summary.html.jinja,sha256=kR4nQc-oGBwXN6buKcf_wPaoWASomR4fT4gJ1iD9hYw,1775
60
+ meridian/templates/table.html.jinja,sha256=ibgnjdKCjS2qbq61lxqwtlb7JigDrc2m42RDdQvQnKQ,1176
61
+ scenarioplanner/__init__.py,sha256=n9eeQGQ0Et7YZ-UZbzBjN1AX5tcOiCHBQnU2qIj0FBk,1782
62
+ scenarioplanner/mmm_ui_proto_generator.py,sha256=4ajGl5bGJiPLid7obnoqP8PI38zJ3vaZo87xeHz7B0A,12657
63
+ scenarioplanner/converters/__init__.py,sha256=I3NNLxDcp137RmZsBp_A1jRrwpH9epzBdXzETgsnUrw,1046
64
+ scenarioplanner/converters/mmm.py,sha256=jEwLwaCzIK7XqpNsGFKeAOZ39wbK5AxQ-iI9SqDwenU,25079
65
+ scenarioplanner/converters/mmm_converter.py,sha256=3r2ARDnxEjd56raF6OkrpQhfvCGbSWcNSt19mH3Y7oc,1732
66
+ scenarioplanner/converters/sheets.py,sha256=KoJim_CKu9sEOwrVg4JlJupykbX3CwTNbblKUJwJxmo,4389
67
+ scenarioplanner/converters/test_data.py,sha256=FuNyXbsOkv10AVJ6fEfmoZ-MUQNhn7hdSKV1SjCSNA0,22607
68
+ scenarioplanner/converters/dataframe/__init__.py,sha256=nmcboP8Sgx9aFoG1sGmhJSh3QiuQjmfzoPNCebLbwQA,1313
69
+ scenarioplanner/converters/dataframe/budget_opt_converters.py,sha256=k0BK4aYQbdUTOh0sHZPP65wCR1vqXT4BosjjS5CP7pI,13183
70
+ scenarioplanner/converters/dataframe/common.py,sha256=UsN_HJY0HYdf5iXmzXVT-rm9Vqlk6t2u2Eb7VjQmF9M,2241
71
+ scenarioplanner/converters/dataframe/constants.py,sha256=6vOOJHntje9gdU7dUZUsEDo-ILXPitCOZqOVpEHH3K8,5509
72
+ scenarioplanner/converters/dataframe/converter.py,sha256=MY5Ihlvg5EUOxhf0zzsVubvATZc8I0ImJ6BP3gjW2Ic,1143
73
+ scenarioplanner/converters/dataframe/dataframe_model_converter.py,sha256=GZLEzKAOol93R_3YntDUCvMmB4ETutYIA6CwEbfk7fA,2472
74
+ scenarioplanner/converters/dataframe/marketing_analyses_converters.py,sha256=yoPvfEalBJUPhWaK7Gbo6OD3q8Z6SI_dUUsEDYFtVEQ,18307
75
+ scenarioplanner/converters/dataframe/rf_opt_converters.py,sha256=WFygfS4aezXRZFKCJajveWih9N1KWargjSDtMqS9vlo,10993
76
+ scenarioplanner/linkingapi/__init__.py,sha256=_GTAn4cJ-jpjEYoCozj240u8M3_ke-T0fAcCacMswVU,1540
77
+ scenarioplanner/linkingapi/constants.py,sha256=e6cNB8OFg74axIcx68r0YsS-alLTDC4pjIGv4CH1N0A,1038
78
+ scenarioplanner/linkingapi/url_generator.py,sha256=kSYHYl1iouLkijoOo1_yRmOldA5GySvg3_9qVd1sBSk,4515
79
+ schema/__init__.py,sha256=cw6HwitjOt8-uAaHHuymCozWSPL18HYlXOHnzjG9iLo,1224
80
+ schema/mmm_proto_generator.py,sha256=3GwsjDwGRmtgyskDXHlzaZ02TIqEtOvttE6Vurf_Fxo,2373
81
+ schema/model_consumer.py,sha256=jYcnnDlhycfBcLKmtjjDVURaZDZXc-TfdD8fMAIm9Kc,4444
82
+ schema/test_data.py,sha256=e1dNCGkbcKg4e0RfiFKxX6bYahhcB7rgB0vmzOYB470,11035
83
+ schema/processors/__init__.py,sha256=Xcw9kTTw5JFtegu3NgKo9CM5nup2PP8vgtIH031ITkI,3704
84
+ schema/processors/budget_optimization_processor.py,sha256=9dAir67Kg6rG3CqCfjWU7VU2HA0DLgjw6PfaPG1HCeg,30600
85
+ schema/processors/common.py,sha256=QpNxYThOvJOiMvt-ilKM5rAiJe7UQCaX6wD6K3e7tJk,1952
86
+ schema/processors/marketing_processor.py,sha256=5UvgTku8vfoePWXb2bRowc34SgqRZnQcxwfEehgVx_8,43332
87
+ schema/processors/model_fit_processor.py,sha256=GkTJWOw9mHoB5vH3ZDkI2K7_9c_-51Lpjwavrer_G08,12597
88
+ schema/processors/model_kernel_processor.py,sha256=gl7BsR7EYnJl88Yxg4qxJvVq1vmAKkjD8NezSbWYLEc,3686
89
+ schema/processors/model_processor.py,sha256=PH4MNFjnjJY4P1ncJbWvFiuItHB0Zp7J2UgwqnnI5h8,14199
90
+ schema/processors/reach_frequency_optimization_processor.py,sha256=uiftX23EXRtMJeZCPu98D8NFFrWT_cLz5eE3dqRLnuw,20551
91
+ schema/serde/__init__.py,sha256=xyydIcWB5IUpcn3wu1m9HL1fK4gMWURbwTyRsQtolF0,975
92
+ schema/serde/constants.py,sha256=aYtD_RuA0GCkpC4TIQq3VjMqEc837Wn-TlJNm-yn_4Y,1842
93
+ schema/serde/distribution.py,sha256=jy3h6JD1TSs4gwociMis814sz_Fm2kFQ2UbkgjYJW9k,19347
94
+ schema/serde/eda_spec.py,sha256=uOqBeZpUU3Dzzc19rU1LjHWmUhRmVcx8oIZvZfVJHT8,7180
95
+ schema/serde/function_registry.py,sha256=GbgC5_9NDcA9Y7nqmdJ-4-LK5JPhhfI50Lmfy5ZBJOg,4858
96
+ schema/serde/hyperparameters.py,sha256=Igm-PZmIozrsKZH6c-XkrU_Nlf8OAuxpnJJfv7W1SfQ,13524
97
+ schema/serde/inference_data.py,sha256=DrwE9hU8LMrl0z8W_sUSIaPrRdym_lu0iOqpT4KZxsA,3623
98
+ schema/serde/marketing_data.py,sha256=yb-fRTe84Sjg7-v3wsvYRRXvrxLSFWSenO0_ikMvUpk,44845
99
+ schema/serde/meridian_serde.py,sha256=ZG05JaBG4LW8mhl-Cunje9Q6xyR4tyNTtLYedzMBYjA,15985
100
+ schema/serde/serde.py,sha256=8vUqhJxvZgX9UY3rXTyWJznRgapwDzzaHXDHwV_kKTA,1612
101
+ schema/serde/test_data.py,sha256=7hfEWyvZ9WcAkVAOXt6elX8stJlsfhfd-ASlHo9SRb8,107342
102
+ schema/utils/__init__.py,sha256=zrMCsxWQIWhsrdgcPSa6UxItIZPAPJK1bhnXhoK1keg,721
103
+ schema/utils/date_range_bucketing.py,sha256=14vcRGf3odWT9mBdCykRNmVCEiuUI_1SvVygNzvqBuM,3809
104
+ schema/utils/time_record.py,sha256=-KzHFjvSBUUXsfESPAfcJP_VFxaFLqj90Ac0kgKWfpI,4624
105
+ google_meridian-1.4.0.dist-info/METADATA,sha256=_PuASfumu3dyvOlvpXGu8MvVWlDpqyZ8ubaMtzLCcBU,9793
106
+ google_meridian-1.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
107
+ google_meridian-1.4.0.dist-info/top_level.txt,sha256=oAi0z-fUuo6p8SnJ0WrojGR2mKOWDz43yr6EjzaXqy8,32
108
+ google_meridian-1.4.0.dist-info/RECORD,,
@@ -1,2 +1,3 @@
1
1
  meridian
2
+ scenarioplanner
2
3
  schema
@@ -15,9 +15,8 @@
15
15
  """Meridian analysis API for trained models."""
16
16
 
17
17
  from meridian.analysis import analyzer
18
- from meridian.analysis import formatter
19
18
  from meridian.analysis import optimizer
20
19
  from meridian.analysis import review
21
20
  from meridian.analysis import summarizer
22
21
  from meridian.analysis import visualizer
23
-
22
+ from meridian.templates import formatter
@@ -53,7 +53,6 @@ def _validate_non_media_baseline_values_numbers(
53
53
  )
54
54
 
55
55
 
56
- # TODO: Refactor the related unit tests to be under DataTensors.
57
56
  @dataclasses.dataclass
58
57
  class DataTensors(backend.ExtensionType):
59
58
  """Container for data variable arguments of Analyzer methods.
@@ -27,10 +27,10 @@ import jinja2
27
27
  from meridian import backend
28
28
  from meridian import constants as c
29
29
  from meridian.analysis import analyzer as analyzer_module
30
- from meridian.analysis import formatter
31
30
  from meridian.analysis import summary_text
32
31
  from meridian.data import time_coordinates as tc
33
32
  from meridian.model import model
33
+ from meridian.templates import formatter
34
34
  import numpy as np
35
35
  import pandas as pd
36
36
  import xarray as xr
@@ -1174,10 +1174,12 @@ class OptimizationResults:
1174
1174
  diff = self.optimized_data.total_cpik - self.nonoptimized_data.total_cpik
1175
1175
  non_optimized_performance_title = summary_text.NON_OPTIMIZED_CPIK_LABEL
1176
1176
  non_optimized_performance_stat = (
1177
- f'${self.nonoptimized_data.total_cpik:.2f}'
1177
+ f'{currency}{self.nonoptimized_data.total_cpik:.2f}'
1178
1178
  )
1179
1179
  optimized_performance_title = summary_text.OPTIMIZED_CPIK_LABEL
1180
- optimized_performance_stat = f'${self.optimized_data.total_cpik:.2f}'
1180
+ optimized_performance_stat = (
1181
+ f'{currency}{self.optimized_data.total_cpik:.2f}'
1182
+ )
1181
1183
  optimized_performance_diff = formatter.compact_number(diff, 2, currency)
1182
1184
  non_optimized_performance = formatter.StatsSpec(
1183
1185
  title=non_optimized_performance_title,
@@ -28,6 +28,7 @@ from meridian.analysis.review import constants as review_constants
28
28
  from meridian.analysis.review import results
29
29
  from meridian.model import model
30
30
  import numpy as np
31
+ import pandas as pd
31
32
 
32
33
  ConfigType = TypeVar("ConfigType", bound=configs.BaseConfig)
33
34
  ResultType = TypeVar("ResultType", bound=results.CheckResult)
@@ -207,6 +208,51 @@ class BayesianPPPCheck(
207
208
  # ==============================================================================
208
209
  # Check: Goodness of Fit
209
210
  # ==============================================================================
211
+ def _set_details_from_gof_dataframe(
212
+ details: dict[str, float],
213
+ gof_df: pd.DataFrame,
214
+ geo_granularity: str,
215
+ suffix: str | None = None,
216
+ ) -> None:
217
+ """Sets the `details` variable of the GoodnessOfFitCheckResult.
218
+
219
+ This method takes a DataFrame containing goodness of fit metrics and pivots it
220
+ to a Series, which is then added to the `details` variable of the
221
+ `GoodnessOfFitCheckResult`.
222
+
223
+ Args:
224
+ details: A dictionary to store the goodness of fit metrics in.
225
+ gof_df: A DataFrame containing predictive accuracy of the whole data (if
226
+ holdout set is not used) of filtered to a single evaluation set ("all",
227
+ "train", or "test").
228
+ geo_granularity: The geo granularity of the data ("geo" or "national").
229
+ suffix: A suffix to add to the metric names (e.g., "all", "train", "test").
230
+ If None, the metrics are added without a suffix.
231
+ """
232
+ gof_metrics_pivoted = gof_df.pivot(
233
+ index=constants.GEO_GRANULARITY,
234
+ columns=constants.METRIC,
235
+ values=constants.VALUE,
236
+ )
237
+ gof_metrics_series = gof_metrics_pivoted.loc[geo_granularity]
238
+ if suffix is not None:
239
+ details[f"{review_constants.R_SQUARED}_{suffix}"] = gof_metrics_series[
240
+ constants.R_SQUARED
241
+ ]
242
+ details[f"{review_constants.MAPE}_{suffix}"] = gof_metrics_series[
243
+ constants.MAPE
244
+ ]
245
+ details[f"{review_constants.WMAPE}_{suffix}"] = gof_metrics_series[
246
+ constants.WMAPE
247
+ ]
248
+ else:
249
+ details[review_constants.R_SQUARED] = gof_metrics_series[
250
+ constants.R_SQUARED
251
+ ]
252
+ details[review_constants.MAPE] = gof_metrics_series[constants.MAPE]
253
+ details[review_constants.WMAPE] = gof_metrics_series[constants.WMAPE]
254
+
255
+
210
256
  class GoodnessOfFitCheck(
211
257
  BaseCheck[configs.GoodnessOfFitConfig, results.GoodnessOfFitCheckResult]
212
258
  ):
@@ -221,38 +267,43 @@ class GoodnessOfFitCheck(
221
267
  )
222
268
 
223
269
  gof_metrics = gof_df[gof_df[constants.GEO_GRANULARITY] == geo_granularity]
224
- if constants.EVALUATION_SET_VAR in gof_df.columns:
225
- gof_metrics = gof_metrics[
226
- gof_metrics[constants.EVALUATION_SET_VAR] == constants.ALL_DATA
227
- ]
228
-
229
- gof_metrics_pivoted = gof_metrics.pivot(
230
- index=constants.GEO_GRANULARITY,
231
- columns=constants.METRIC,
232
- values=constants.VALUE,
233
- )
234
- gof_metrics_series = gof_metrics_pivoted.loc[geo_granularity]
235
-
236
- r_squared = gof_metrics_series[constants.R_SQUARED]
237
- mape = gof_metrics_series[constants.MAPE]
238
- wmape = gof_metrics_series[constants.WMAPE]
239
-
240
- details = {
241
- review_constants.R_SQUARED: r_squared,
242
- review_constants.MAPE: mape,
243
- review_constants.WMAPE: wmape,
244
- }
245
-
246
- if r_squared > 0:
247
- return results.GoodnessOfFitCheckResult(
248
- case=results.GoodnessOfFitCases.PASS,
249
- details=details,
250
- )
251
- else: # r_squared <= 0
252
- return results.GoodnessOfFitCheckResult(
253
- case=results.GoodnessOfFitCases.REVIEW,
270
+ is_holdout = constants.EVALUATION_SET_VAR in gof_df.columns
271
+
272
+ details = {}
273
+ case = results.GoodnessOfFitCases.PASS
274
+
275
+ if is_holdout:
276
+ for evaluation_set, suffix in [
277
+ (constants.ALL_DATA, review_constants.ALL_SUFFIX),
278
+ (constants.TRAIN, review_constants.TRAIN_SUFFIX),
279
+ (constants.TEST, review_constants.TEST_SUFFIX),
280
+ ]:
281
+ set_metrics = gof_metrics[
282
+ gof_metrics[constants.EVALUATION_SET_VAR] == evaluation_set
283
+ ]
284
+ _set_details_from_gof_dataframe(
285
+ details=details,
286
+ gof_df=set_metrics,
287
+ geo_granularity=geo_granularity,
288
+ suffix=suffix,
289
+ )
290
+ if details[f"{review_constants.R_SQUARED}_{suffix}"] <= 0:
291
+ case = results.GoodnessOfFitCases.REVIEW
292
+ else:
293
+ _set_details_from_gof_dataframe(
254
294
  details=details,
295
+ gof_df=gof_metrics,
296
+ geo_granularity=geo_granularity,
297
+ suffix=None,
255
298
  )
299
+ if details[review_constants.R_SQUARED] <= 0:
300
+ case = results.GoodnessOfFitCases.REVIEW
301
+
302
+ return results.GoodnessOfFitCheckResult(
303
+ case=case,
304
+ details=details,
305
+ is_holdout=is_holdout,
306
+ )
256
307
 
257
308
 
258
309
  # ==============================================================================
@@ -32,6 +32,10 @@ NEGATIVE_BASELINE_PROB_REVIEW_THRESHOLD = (
32
32
  R_SQUARED = "r_squared"
33
33
  MAPE = "mape"
34
34
  WMAPE = "wmape"
35
+ ALL_SUFFIX = "all"
36
+ TRAIN_SUFFIX = "train"
37
+ TEST_SUFFIX = "test"
38
+ EVALUATION_SET_SUFFIXES = (ALL_SUFFIX, TRAIN_SUFFIX, TEST_SUFFIX)
35
39
  MEAN = "mean"
36
40
  VARIANCE = "variance"
37
41
  MEDIAN = "median"
@@ -319,18 +319,12 @@ class GoodnessOfFitCases(ModelCheckCase, enum.Enum):
319
319
 
320
320
  PASS = (
321
321
  Status.PASS,
322
- (
323
- "R-squared = {r_squared:.4f}, MAPE = {mape:.4f}, and wMAPE ="
324
- " {wmape:.4f}."
325
- ),
322
+ "R-squared = {r_squared:.4f}, MAPE = {mape:.4f}, and wMAPE = {wmape:.4f}",
326
323
  _GOODNESS_OF_FIT_PASS_RECOMMENDATION,
327
324
  )
328
325
  REVIEW = (
329
326
  Status.REVIEW,
330
- (
331
- "R-squared = {r_squared:.4f}, MAPE = {mape:.4f}, and wMAPE ="
332
- " {wmape:.4f}."
333
- ),
327
+ "R-squared = {r_squared:.4f}, MAPE = {mape:.4f}, and wMAPE = {wmape:.4f}",
334
328
  _GOODNESS_OF_FIT_REVIEW_RECOMMENDATION,
335
329
  )
336
330
 
@@ -348,9 +342,28 @@ class GoodnessOfFitCheckResult(CheckResult):
348
342
  """The immutable result of the Goodness of Fit Check."""
349
343
 
350
344
  case: GoodnessOfFitCases
345
+ is_holdout: bool = False
351
346
 
352
347
  def __post_init__(self):
353
- if any(
348
+ if self.is_holdout:
349
+ required_keys = []
350
+ for suffix in [
351
+ constants.ALL_SUFFIX,
352
+ constants.TRAIN_SUFFIX,
353
+ constants.TEST_SUFFIX,
354
+ ]:
355
+ required_keys.extend([
356
+ f"{constants.R_SQUARED}_{suffix}",
357
+ f"{constants.MAPE}_{suffix}",
358
+ f"{constants.WMAPE}_{suffix}",
359
+ ])
360
+ if any(key not in self.details for key in required_keys):
361
+ raise ValueError(
362
+ "The message template is missing required formatting arguments for"
363
+ f" holdout case. Required keys: {required_keys}. Details:"
364
+ f" {self.details}."
365
+ )
366
+ elif any(
354
367
  key not in self.details
355
368
  for key in (
356
369
  constants.R_SQUARED,
@@ -364,6 +377,24 @@ class GoodnessOfFitCheckResult(CheckResult):
364
377
  f" {self.details}."
365
378
  )
366
379
 
380
+ @property
381
+ def recommendation(self) -> str:
382
+ """Returns the check result message."""
383
+ if self.is_holdout:
384
+ report_str = (
385
+ "R-squared = {r_squared_all:.4f} (All),"
386
+ " {r_squared_train:.4f} (Train), {r_squared_test:.4f} (Test); MAPE"
387
+ " = {mape_all:.4f} (All), {mape_train:.4f} (Train),"
388
+ " {mape_test:.4f} (Test); wMAPE = {wmape_all:.4f} (All),"
389
+ " {wmape_train:.4f} (Train), {wmape_test:.4f} (Test)".format(
390
+ **self.details
391
+ )
392
+ )
393
+ else:
394
+ report_str = self.case.message_template.format(**self.details)
395
+
396
+ return f"{report_str}. {self.case.recommendation}"
397
+
367
398
 
368
399
  # ==============================================================================
369
400
  # Check: ROI Consistency
@@ -21,11 +21,11 @@ import os
21
21
  import jinja2
22
22
  from meridian import constants as c
23
23
  from meridian.analysis import analyzer
24
- from meridian.analysis import formatter
25
24
  from meridian.analysis import summary_text
26
25
  from meridian.analysis import visualizer
27
26
  from meridian.data import time_coordinates as tc
28
27
  from meridian.model import model
28
+ from meridian.templates import formatter
29
29
  import pandas as pd
30
30
  import xarray as xr
31
31
 
@@ -318,7 +318,7 @@ class Summarizer:
318
318
  chart_json=media_summary.plot_contribution_waterfall_chart().to_json(),
319
319
  )
320
320
  lead_channels = self._get_sorted_posterior_mean_metrics_df(
321
- media_summary, [c.INCREMENTAL_OUTCOME]
321
+ media_summary, [c.INCREMENTAL_OUTCOME], include_non_paid_channels=True
322
322
  )[c.CHANNEL][:2]
323
323
  formatted_channels = [channel.title() for channel in lead_channels]
324
324
 
@@ -358,9 +358,14 @@ class Summarizer:
358
358
  media_summary: visualizer.MediaSummary,
359
359
  metrics: Sequence[str],
360
360
  ascending: bool = False,
361
+ include_non_paid_channels: bool = False,
361
362
  ) -> pd.DataFrame:
363
+ if include_non_paid_channels:
364
+ summary_metrics = media_summary.get_all_summary_metrics()
365
+ else:
366
+ summary_metrics = media_summary.get_paid_summary_metrics()
362
367
  return (
363
- media_summary.get_paid_summary_metrics()[metrics]
368
+ summary_metrics[metrics]
364
369
  .sel(distribution=c.POSTERIOR, metric=c.MEAN)
365
370
  .drop_sel(channel=c.ALL_CHANNELS)
366
371
  .to_dataframe()