[{"data":1,"prerenderedAt":1060},["ShallowReactive",2],{"article-alternates":3,"article-\u002Fen\u002Fdata\u002Fmarketing-mix-modeling-practical-setup-with-robyn":13},{"i18nKey":4,"paths":5},"data-005-2026-06",{"de":6,"en":7,"es":8,"fr":9,"it":10,"ru":11,"tr":12},"\u002Fde\u002Fdata\u002Fmarketing-mix-modeling-robyn-praktische-einrichtung","\u002Fen\u002Fdata\u002Fmarketing-mix-modeling-practical-setup-with-robyn","\u002Fes\u002Fdata\u002Fmarketing-mix-modeling-robyn-setup-practico","\u002Ffr\u002Fdata\u002Frobyn-marketing-mix-modeling-guide","\u002Fit\u002Fdata\u002Frobyn-marketing-mix-modeling-setup-pratico","\u002Fru\u002Fdata\u002Fmarketing-mix-modeling-robyn-prakticheskaya-setup","\u002Ftr\u002Fdata\u002Fmarketing-mix-modeling-robyn-ile-pratik-kurulum",{"_path":7,"_dir":14,"_draft":15,"_partial":15,"_locale":16,"title":17,"description":18,"publishedAt":19,"modifiedAt":19,"category":14,"i18nKey":4,"tags":20,"readingTime":26,"author":27,"body":28,"_type":1054,"_id":1055,"_source":1056,"_file":1057,"_stem":1058,"_extension":1059},"data",false,"","Marketing Mix Modeling: Practical Setup with Robyn","Meta's open-source MMM framework Robyn enables saturation, adstock, and holdout validation with practical R code and correct data structures for post-cookie measurement.","2026-06-05",[21,22,23,24,25],"marketing-mix-modeling","robyn","adstock","saturation-curve","incrementality",8,"Roibase",{"type":29,"children":30,"toc":1046},"root",[31,47,54,59,91,103,109,114,225,233,316,337,361,367,379,387,407,412,420,438,532,537,543,563,764,776,781,835,840,846,872,885,954,966,972,991,1011,1023,1035,1040],{"type":32,"tag":33,"props":34,"children":35},"element","p",{},[36,39,45],{"type":37,"value":38},"text","In the post-cookie measurement world, attribution loses a little more signal every day. With iOS 17.4 and SKAdNetwork struggling to surface true ROAS, marketing budget owners are turning to econometric models to measure channels' actual contribution. Marketing Mix Modeling (MMM), a statistical method developed in the 1960s for television advertising, has reclaimed center stage in 2026 alongside server-side measurement and first-party data lakes. ",{"type":32,"tag":40,"props":41,"children":42},"strong",{},[43],{"type":37,"value":44},"Robyn",{"type":37,"value":46},", released as open source by Meta in 2021, accelerated adoption by adding modern machine learning and Bayesian optimization to this regression-based methodology.",{"type":32,"tag":48,"props":49,"children":51},"h2",{"id":50},"why-mmm-is-critical-now",[52],{"type":37,"value":53},"Why MMM is critical now",{"type":32,"tag":33,"props":55,"children":56},{},[57],{"type":37,"value":58},"As the last-click attribution model collapses with cookie loss, multi-touch attribution (MTA) has become unusable due to event-level data requirements in the GDPR and ATT era. Google Analytics 4's data-driven attribution relies on machine learning but operates only within the Google ecosystem. Yet 60% of marketing budgets still sit outside Google: Meta, TikTok, programmatic display, offline TV, sponsorships.",{"type":32,"tag":33,"props":60,"children":61},{},[62,64,69,71,75,77,82,84,89],{"type":37,"value":63},"MMM relies on aggregated data at the weekly or daily level rather than user-level tracking. Regression models extract the relationship between each channel's spend and sales (or conversions). Two core assumptions underpin the model: ",{"type":32,"tag":40,"props":65,"children":66},{},[67],{"type":37,"value":68},"saturation",{"type":37,"value":70}," (increasing spend yields diminishing marginal returns) and ",{"type":32,"tag":40,"props":72,"children":73},{},[74],{"type":37,"value":23},{"type":37,"value":76}," (today's ad influences future weeks). These are statistical assumptions grounded in business reality. Robyn targets automatic parameter discovery through Bayesian hyperparameter optimization. Post-2024 releases (v3.11+) added ",{"type":32,"tag":40,"props":78,"children":79},{},[80],{"type":37,"value":81},"ridge regression",{"type":37,"value":83}," and ",{"type":32,"tag":40,"props":85,"children":86},{},[87],{"type":37,"value":88},"Prophet time-series decomposition",{"type":37,"value":90},", improving seasonal accuracy.",{"type":32,"tag":33,"props":92,"children":93},{},[94,96,101],{"type":37,"value":95},"Another critical Robyn feature is ",{"type":32,"tag":40,"props":97,"children":98},{},[99],{"type":37,"value":100},"holdout validation",{"type":37,"value":102},": the model trains on the prior 12 weeks of data and predicts the next four weeks to measure out-of-sample error. This guards against overfitting and confirms the model actually learned channels. Google's Meridian and Facebook's legacy MMM solutions use similar approaches but remain closed-source and expensive. Robyn delivers the same methodology at no cost.",{"type":32,"tag":48,"props":104,"children":106},{"id":105},"data-structure-and-preparation",[107],{"type":37,"value":108},"Data structure and preparation",{"type":32,"tag":33,"props":110,"children":111},{},[112],{"type":37,"value":113},"Robyn requires data in a specific format: each row represents a time unit (day or week), and each column represents channel spend or a conversion metric. A minimum of 104 weeks (two years) is recommended because regression coefficient significance depends on sample size. With fewer than 52 weeks, you'll face convergence issues.",{"type":32,"tag":115,"props":116,"children":120},"pre",{"code":117,"language":118,"meta":16,"className":119,"style":16},"# Example data structure — aggregated weekly from BigQuery\ndf \u003C- data.frame(\n  DATE = seq.Date(from = as.Date(\"2024-01-01\"), by = \"week\", length.out = 104),\n  revenue = runif(104, 80000, 150000),\n  google_search_spend = runif(104, 5000, 15000),\n  meta_spend = runif(104, 8000, 20000),\n  tiktok_spend = runif(104, 2000, 8000),\n  tv_grp = runif(104, 50, 200),\n  organic_sessions = runif(104, 10000, 30000),\n  competitor_index = runif(104, 0.8, 1.2)\n)\n","r","language-r shiki shiki-themes github-dark",[121],{"type":32,"tag":122,"props":123,"children":124},"code",{"__ignoreMap":16},[125,136,145,154,163,172,181,190,198,207,216],{"type":32,"tag":126,"props":127,"children":130},"span",{"class":128,"line":129},"line",1,[131],{"type":32,"tag":126,"props":132,"children":133},{},[134],{"type":37,"value":135},"# Example data structure — aggregated weekly from BigQuery\n",{"type":32,"tag":126,"props":137,"children":139},{"class":128,"line":138},2,[140],{"type":32,"tag":126,"props":141,"children":142},{},[143],{"type":37,"value":144},"df \u003C- data.frame(\n",{"type":32,"tag":126,"props":146,"children":148},{"class":128,"line":147},3,[149],{"type":32,"tag":126,"props":150,"children":151},{},[152],{"type":37,"value":153},"  DATE = seq.Date(from = as.Date(\"2024-01-01\"), by = \"week\", length.out = 104),\n",{"type":32,"tag":126,"props":155,"children":157},{"class":128,"line":156},4,[158],{"type":32,"tag":126,"props":159,"children":160},{},[161],{"type":37,"value":162},"  revenue = runif(104, 80000, 150000),\n",{"type":32,"tag":126,"props":164,"children":166},{"class":128,"line":165},5,[167],{"type":32,"tag":126,"props":168,"children":169},{},[170],{"type":37,"value":171},"  google_search_spend = runif(104, 5000, 15000),\n",{"type":32,"tag":126,"props":173,"children":175},{"class":128,"line":174},6,[176],{"type":32,"tag":126,"props":177,"children":178},{},[179],{"type":37,"value":180},"  meta_spend = runif(104, 8000, 20000),\n",{"type":32,"tag":126,"props":182,"children":184},{"class":128,"line":183},7,[185],{"type":32,"tag":126,"props":186,"children":187},{},[188],{"type":37,"value":189},"  tiktok_spend = runif(104, 2000, 8000),\n",{"type":32,"tag":126,"props":191,"children":192},{"class":128,"line":26},[193],{"type":32,"tag":126,"props":194,"children":195},{},[196],{"type":37,"value":197},"  tv_grp = runif(104, 50, 200),\n",{"type":32,"tag":126,"props":199,"children":201},{"class":128,"line":200},9,[202],{"type":32,"tag":126,"props":203,"children":204},{},[205],{"type":37,"value":206},"  organic_sessions = runif(104, 10000, 30000),\n",{"type":32,"tag":126,"props":208,"children":210},{"class":128,"line":209},10,[211],{"type":32,"tag":126,"props":212,"children":213},{},[214],{"type":37,"value":215},"  competitor_index = runif(104, 0.8, 1.2)\n",{"type":32,"tag":126,"props":217,"children":219},{"class":128,"line":218},11,[220],{"type":32,"tag":126,"props":221,"children":222},{},[223],{"type":37,"value":224},")\n",{"type":32,"tag":33,"props":226,"children":227},{},[228],{"type":32,"tag":40,"props":229,"children":230},{},[231],{"type":37,"value":232},"Critical details:",{"type":32,"tag":234,"props":235,"children":236},"ul",{},[237,251,256,284,311],{"type":32,"tag":238,"props":239,"children":240},"li",{},[241,243,249],{"type":37,"value":242},"The ",{"type":32,"tag":122,"props":244,"children":246},{"className":245},[],[247],{"type":37,"value":248},"DATE",{"type":37,"value":250}," column must be Date class, not string",{"type":32,"tag":238,"props":252,"children":253},{},[254],{"type":37,"value":255},"Revenue or conversion serves as the dependent variable fed into the model",{"type":32,"tag":238,"props":257,"children":258},{},[259,261,267,269,275,277,282],{"type":37,"value":260},"Channels (",{"type":32,"tag":122,"props":262,"children":264},{"className":263},[],[265],{"type":37,"value":266},"google_search_spend",{"type":37,"value":268},", ",{"type":32,"tag":122,"props":270,"children":272},{"className":271},[],[273],{"type":37,"value":274},"meta_spend",{"type":37,"value":276},") are ",{"type":32,"tag":40,"props":278,"children":279},{},[280],{"type":37,"value":281},"paid",{"type":37,"value":283}," media columns—adstock and saturation apply to these",{"type":32,"tag":238,"props":285,"children":286},{},[287,289,295,296,302,304,309],{"type":37,"value":288},"Variables like ",{"type":32,"tag":122,"props":290,"children":292},{"className":291},[],[293],{"type":37,"value":294},"organic_sessions",{"type":37,"value":83},{"type":32,"tag":122,"props":297,"children":299},{"className":298},[],[300],{"type":37,"value":301},"competitor_index",{"type":37,"value":303}," are ",{"type":32,"tag":40,"props":305,"children":306},{},[307],{"type":37,"value":308},"organic\u002Fcontrol",{"type":37,"value":310}," variables—no conversion function applies; they enter baseline inference",{"type":32,"tag":238,"props":312,"children":313},{},[314],{"type":37,"value":315},"For offline channels like TV, use GRP, reach, or viewing minutes as normalized input",{"type":32,"tag":33,"props":317,"children":318},{},[319,321,327,329,335],{"type":37,"value":320},"Robyn doesn't work with manual labels like ",{"type":32,"tag":122,"props":322,"children":324},{"className":323},[],[325],{"type":37,"value":326},"facebook_spend",{"type":37,"value":328},"; you define column names yourself, but in the ",{"type":32,"tag":122,"props":330,"children":332},{"className":331},[],[333],{"type":37,"value":334},"InputCollect()",{"type":37,"value":336}," function you must explicitly specify which columns are paid and which are organic.",{"type":32,"tag":33,"props":338,"children":339},{},[340,342,351,353,359],{"type":37,"value":341},"If you haven't built a ",{"type":32,"tag":343,"props":344,"children":348},"a",{"href":345,"rel":346},"https:\u002F\u002Fwww.roibase.com.tr\u002Fen\u002Ffirstparty",[347],"nofollow",[349],{"type":37,"value":350},"first-party data architecture",{"type":37,"value":352},", collecting this data is difficult. Server-side GTM, GA4 raw export, Meta\u002FGoogle Ads APIs, sales data from your CRM—combine everything in BigQuery and roll up to weekly granularity. When we build this ETL pipeline with dbt, we produce a ready-to-use ",{"type":32,"tag":122,"props":354,"children":356},{"className":355},[],[357],{"type":37,"value":358},"fact_marketing_weekly",{"type":37,"value":360}," table for MMM.",{"type":32,"tag":48,"props":362,"children":364},{"id":363},"saturation-and-adstock-configuration",[365],{"type":37,"value":366},"Saturation and adstock configuration",{"type":32,"tag":33,"props":368,"children":369},{},[370,372,377],{"type":37,"value":371},"Robyn's strength lies in optimizing saturation curves and adstock decay parameters ",{"type":32,"tag":40,"props":373,"children":374},{},[375],{"type":37,"value":376},"individually per channel",{"type":37,"value":378},". Saturation is modeled using the Hill function:",{"type":32,"tag":115,"props":380,"children":382},{"code":381},"effect = spend^alpha \u002F (spend^alpha + half_saturation^alpha)\n",[383],{"type":32,"tag":122,"props":384,"children":385},{"__ignoreMap":16},[386],{"type":37,"value":381},{"type":32,"tag":33,"props":388,"children":389},{},[390,391,397,399,405],{"type":37,"value":242},{"type":32,"tag":122,"props":392,"children":394},{"className":393},[],[395],{"type":37,"value":396},"alpha",{"type":37,"value":398}," parameter controls curve concavity; ",{"type":32,"tag":122,"props":400,"children":402},{"className":401},[],[403],{"type":37,"value":404},"half_saturation",{"type":37,"value":406}," defines the spend level at which half the effect materializes. Intent-based channels like Google Search saturate early (low alpha, low half_saturation). Awareness channels (TV, YouTube) saturate late.",{"type":32,"tag":33,"props":408,"children":409},{},[410],{"type":37,"value":411},"Adstock models past spend's current impact. Geometric adstock is most common:",{"type":32,"tag":115,"props":413,"children":415},{"code":414},"adstocked_spend[t] = spend[t] + theta * adstocked_spend[t-1]\n",[416],{"type":32,"tag":122,"props":417,"children":418},{"__ignoreMap":16},[419],{"type":37,"value":414},{"type":32,"tag":33,"props":421,"children":422},{},[423,429,431,436],{"type":32,"tag":122,"props":424,"children":426},{"className":425},[],[427],{"type":37,"value":428},"theta",{"type":37,"value":430}," (between 0 and 1) is the decay rate. TV has high theta (0.7–0.9 — effects persist for weeks); search has low theta (0.1–0.3 — effects terminate quickly). Robyn finds these parameters via Nevergrad optimization, but you must provide ",{"type":32,"tag":40,"props":432,"children":433},{},[434],{"type":37,"value":435},"prior ranges",{"type":37,"value":437},":",{"type":32,"tag":115,"props":439,"children":441},{"code":440,"language":118,"meta":16,"className":119,"style":16},"hyperparameters \u003C- list(\n  google_search_spend_alphas = c(0.5, 1.5),\n  google_search_spend_gammas = c(0.1, 0.4), # adstock decay\n  google_search_spend_thetas = c(0, 0.3),   # adstock theta\n  meta_spend_alphas = c(0.5, 2.0),\n  meta_spend_gammas = c(0.3, 0.8),\n  meta_spend_thetas = c(0.2, 0.6),\n  tv_grp_alphas = c(1.0, 3.0),\n  tv_grp_gammas = c(0.5, 0.9),\n  tv_grp_thetas = c(0.6, 0.9)\n)\n",[442],{"type":32,"tag":122,"props":443,"children":444},{"__ignoreMap":16},[445,453,461,469,477,485,493,501,509,517,525],{"type":32,"tag":126,"props":446,"children":447},{"class":128,"line":129},[448],{"type":32,"tag":126,"props":449,"children":450},{},[451],{"type":37,"value":452},"hyperparameters \u003C- list(\n",{"type":32,"tag":126,"props":454,"children":455},{"class":128,"line":138},[456],{"type":32,"tag":126,"props":457,"children":458},{},[459],{"type":37,"value":460},"  google_search_spend_alphas = c(0.5, 1.5),\n",{"type":32,"tag":126,"props":462,"children":463},{"class":128,"line":147},[464],{"type":32,"tag":126,"props":465,"children":466},{},[467],{"type":37,"value":468},"  google_search_spend_gammas = c(0.1, 0.4), # adstock decay\n",{"type":32,"tag":126,"props":470,"children":471},{"class":128,"line":156},[472],{"type":32,"tag":126,"props":473,"children":474},{},[475],{"type":37,"value":476},"  google_search_spend_thetas = c(0, 0.3),   # adstock theta\n",{"type":32,"tag":126,"props":478,"children":479},{"class":128,"line":165},[480],{"type":32,"tag":126,"props":481,"children":482},{},[483],{"type":37,"value":484},"  meta_spend_alphas = c(0.5, 2.0),\n",{"type":32,"tag":126,"props":486,"children":487},{"class":128,"line":174},[488],{"type":32,"tag":126,"props":489,"children":490},{},[491],{"type":37,"value":492},"  meta_spend_gammas = c(0.3, 0.8),\n",{"type":32,"tag":126,"props":494,"children":495},{"class":128,"line":183},[496],{"type":32,"tag":126,"props":497,"children":498},{},[499],{"type":37,"value":500},"  meta_spend_thetas = c(0.2, 0.6),\n",{"type":32,"tag":126,"props":502,"children":503},{"class":128,"line":26},[504],{"type":32,"tag":126,"props":505,"children":506},{},[507],{"type":37,"value":508},"  tv_grp_alphas = c(1.0, 3.0),\n",{"type":32,"tag":126,"props":510,"children":511},{"class":128,"line":200},[512],{"type":32,"tag":126,"props":513,"children":514},{},[515],{"type":37,"value":516},"  tv_grp_gammas = c(0.5, 0.9),\n",{"type":32,"tag":126,"props":518,"children":519},{"class":128,"line":209},[520],{"type":32,"tag":126,"props":521,"children":522},{},[523],{"type":37,"value":524},"  tv_grp_thetas = c(0.6, 0.9)\n",{"type":32,"tag":126,"props":526,"children":527},{"class":128,"line":218},[528],{"type":32,"tag":126,"props":529,"children":530},{},[531],{"type":37,"value":224},{"type":32,"tag":33,"props":533,"children":534},{},[535],{"type":37,"value":536},"Set these ranges using domain knowledge. Arbitrary ranges cause divergence or nonsensical coefficients (e.g., negative TV impact). Robyn's documentation suggests defaults, but test them on your data before deploying.",{"type":32,"tag":48,"props":538,"children":540},{"id":539},"model-training-and-holdout-validation",[541],{"type":37,"value":542},"Model training and holdout validation",{"type":32,"tag":33,"props":544,"children":545},{},[546,548,554,556,561],{"type":37,"value":547},"You run Robyn using the ",{"type":32,"tag":122,"props":549,"children":551},{"className":550},[],[552],{"type":37,"value":553},"robyn_run()",{"type":37,"value":555}," function. Inside, ",{"type":32,"tag":40,"props":557,"children":558},{},[559],{"type":37,"value":560},"Nevergrad",{"type":37,"value":562}," performs Bayesian optimization to find the best hyperparameter combination. A typical run means 2,000 iterations × 10 trials = 20,000 model trainings. On an M1 MacBook with 8 cores, expect ~15 minutes.",{"type":32,"tag":115,"props":564,"children":566},{"code":565,"language":118,"meta":16,"className":119,"style":16},"library(Robyn)\n\nInputCollect \u003C- robyn_inputs(\n  dt_input = df,\n  date_var = \"DATE\",\n  dep_var = \"revenue\",\n  dep_var_type = \"revenue\",\n  paid_media_vars = c(\"google_search_spend\", \"meta_spend\", \"tiktok_spend\"),\n  paid_media_spends = c(\"google_search_spend\", \"meta_spend\", \"tiktok_spend\"),\n  organic_vars = c(\"organic_sessions\"),\n  prophet_vars = c(\"trend\", \"season\", \"holiday\"),\n  window_start = \"2024-01-01\",\n  window_end = \"2025-12-31\",\n  adstock = \"geometric\",\n  hyperparameters = hyperparameters\n)\n\nOutputModels \u003C- robyn_run(\n  InputCollect = InputCollect,\n  iterations = 2000,\n  trials = 10,\n  outputs = FALSE\n)\n",[567],{"type":32,"tag":122,"props":568,"children":569},{"__ignoreMap":16},[570,578,587,595,603,611,619,627,635,643,651,659,668,677,686,695,703,711,720,729,738,747,756],{"type":32,"tag":126,"props":571,"children":572},{"class":128,"line":129},[573],{"type":32,"tag":126,"props":574,"children":575},{},[576],{"type":37,"value":577},"library(Robyn)\n",{"type":32,"tag":126,"props":579,"children":580},{"class":128,"line":138},[581],{"type":32,"tag":126,"props":582,"children":584},{"emptyLinePlaceholder":583},true,[585],{"type":37,"value":586},"\n",{"type":32,"tag":126,"props":588,"children":589},{"class":128,"line":147},[590],{"type":32,"tag":126,"props":591,"children":592},{},[593],{"type":37,"value":594},"InputCollect \u003C- robyn_inputs(\n",{"type":32,"tag":126,"props":596,"children":597},{"class":128,"line":156},[598],{"type":32,"tag":126,"props":599,"children":600},{},[601],{"type":37,"value":602},"  dt_input = df,\n",{"type":32,"tag":126,"props":604,"children":605},{"class":128,"line":165},[606],{"type":32,"tag":126,"props":607,"children":608},{},[609],{"type":37,"value":610},"  date_var = \"DATE\",\n",{"type":32,"tag":126,"props":612,"children":613},{"class":128,"line":174},[614],{"type":32,"tag":126,"props":615,"children":616},{},[617],{"type":37,"value":618},"  dep_var = \"revenue\",\n",{"type":32,"tag":126,"props":620,"children":621},{"class":128,"line":183},[622],{"type":32,"tag":126,"props":623,"children":624},{},[625],{"type":37,"value":626},"  dep_var_type = \"revenue\",\n",{"type":32,"tag":126,"props":628,"children":629},{"class":128,"line":26},[630],{"type":32,"tag":126,"props":631,"children":632},{},[633],{"type":37,"value":634},"  paid_media_vars = c(\"google_search_spend\", \"meta_spend\", \"tiktok_spend\"),\n",{"type":32,"tag":126,"props":636,"children":637},{"class":128,"line":200},[638],{"type":32,"tag":126,"props":639,"children":640},{},[641],{"type":37,"value":642},"  paid_media_spends = c(\"google_search_spend\", \"meta_spend\", \"tiktok_spend\"),\n",{"type":32,"tag":126,"props":644,"children":645},{"class":128,"line":209},[646],{"type":32,"tag":126,"props":647,"children":648},{},[649],{"type":37,"value":650},"  organic_vars = c(\"organic_sessions\"),\n",{"type":32,"tag":126,"props":652,"children":653},{"class":128,"line":218},[654],{"type":32,"tag":126,"props":655,"children":656},{},[657],{"type":37,"value":658},"  prophet_vars = c(\"trend\", \"season\", \"holiday\"),\n",{"type":32,"tag":126,"props":660,"children":662},{"class":128,"line":661},12,[663],{"type":32,"tag":126,"props":664,"children":665},{},[666],{"type":37,"value":667},"  window_start = \"2024-01-01\",\n",{"type":32,"tag":126,"props":669,"children":671},{"class":128,"line":670},13,[672],{"type":32,"tag":126,"props":673,"children":674},{},[675],{"type":37,"value":676},"  window_end = \"2025-12-31\",\n",{"type":32,"tag":126,"props":678,"children":680},{"class":128,"line":679},14,[681],{"type":32,"tag":126,"props":682,"children":683},{},[684],{"type":37,"value":685},"  adstock = \"geometric\",\n",{"type":32,"tag":126,"props":687,"children":689},{"class":128,"line":688},15,[690],{"type":32,"tag":126,"props":691,"children":692},{},[693],{"type":37,"value":694},"  hyperparameters = hyperparameters\n",{"type":32,"tag":126,"props":696,"children":698},{"class":128,"line":697},16,[699],{"type":32,"tag":126,"props":700,"children":701},{},[702],{"type":37,"value":224},{"type":32,"tag":126,"props":704,"children":706},{"class":128,"line":705},17,[707],{"type":32,"tag":126,"props":708,"children":709},{"emptyLinePlaceholder":583},[710],{"type":37,"value":586},{"type":32,"tag":126,"props":712,"children":714},{"class":128,"line":713},18,[715],{"type":32,"tag":126,"props":716,"children":717},{},[718],{"type":37,"value":719},"OutputModels \u003C- robyn_run(\n",{"type":32,"tag":126,"props":721,"children":723},{"class":128,"line":722},19,[724],{"type":32,"tag":126,"props":725,"children":726},{},[727],{"type":37,"value":728},"  InputCollect = InputCollect,\n",{"type":32,"tag":126,"props":730,"children":732},{"class":128,"line":731},20,[733],{"type":32,"tag":126,"props":734,"children":735},{},[736],{"type":37,"value":737},"  iterations = 2000,\n",{"type":32,"tag":126,"props":739,"children":741},{"class":128,"line":740},21,[742],{"type":32,"tag":126,"props":743,"children":744},{},[745],{"type":37,"value":746},"  trials = 10,\n",{"type":32,"tag":126,"props":748,"children":750},{"class":128,"line":749},22,[751],{"type":32,"tag":126,"props":752,"children":753},{},[754],{"type":37,"value":755},"  outputs = FALSE\n",{"type":32,"tag":126,"props":757,"children":759},{"class":128,"line":758},23,[760],{"type":32,"tag":126,"props":761,"children":762},{},[763],{"type":37,"value":224},{"type":32,"tag":33,"props":765,"children":766},{},[767,769,774],{"type":37,"value":768},"After training, the model surfaces ",{"type":32,"tag":40,"props":770,"children":771},{},[772],{"type":37,"value":773},"Pareto-optimal",{"type":37,"value":775}," solutions. Robyn optimizes two metrics: NRMSE (normalized root mean square error) and decomposition RSSD (residual sum of squared differences). Each model on the Pareto frontier represents a trade-off: one fits well but has poor decomposition; another is the reverse. You manually select the most reasonable model.",{"type":32,"tag":33,"props":777,"children":778},{},[779],{"type":37,"value":780},"For holdout validation, you reserve the final 4–8 weeks. Robyn automates this:",{"type":32,"tag":115,"props":782,"children":784},{"code":783,"language":118,"meta":16,"className":119,"style":16},"robyn_refresh(\n  robyn_object = OutputModels,\n  dt_input = df_new, # Refresh with new data\n  refresh_steps = 4,\n  refresh_mode = \"manual\"\n)\n",[785],{"type":32,"tag":122,"props":786,"children":787},{"__ignoreMap":16},[788,796,804,812,820,828],{"type":32,"tag":126,"props":789,"children":790},{"class":128,"line":129},[791],{"type":32,"tag":126,"props":792,"children":793},{},[794],{"type":37,"value":795},"robyn_refresh(\n",{"type":32,"tag":126,"props":797,"children":798},{"class":128,"line":138},[799],{"type":32,"tag":126,"props":800,"children":801},{},[802],{"type":37,"value":803},"  robyn_object = OutputModels,\n",{"type":32,"tag":126,"props":805,"children":806},{"class":128,"line":147},[807],{"type":32,"tag":126,"props":808,"children":809},{},[810],{"type":37,"value":811},"  dt_input = df_new, # Refresh with new data\n",{"type":32,"tag":126,"props":813,"children":814},{"class":128,"line":156},[815],{"type":32,"tag":126,"props":816,"children":817},{},[818],{"type":37,"value":819},"  refresh_steps = 4,\n",{"type":32,"tag":126,"props":821,"children":822},{"class":128,"line":165},[823],{"type":32,"tag":126,"props":824,"children":825},{},[826],{"type":37,"value":827},"  refresh_mode = \"manual\"\n",{"type":32,"tag":126,"props":829,"children":830},{"class":128,"line":174},[831],{"type":32,"tag":126,"props":832,"children":833},{},[834],{"type":37,"value":224},{"type":32,"tag":33,"props":836,"children":837},{},[838],{"type":37,"value":839},"If holdout MAPE (mean absolute percentage error) falls below 10%, the model is trustworthy. Above 20% is dangerous—signals overfitting or missing variables.",{"type":32,"tag":48,"props":841,"children":843},{"id":842},"interpreting-outputs-and-budget-optimization",[844],{"type":37,"value":845},"Interpreting outputs and budget optimization",{"type":32,"tag":33,"props":847,"children":848},{},[849,851,856,858,863,865,870],{"type":37,"value":850},"Robyn's most critical output is the ",{"type":32,"tag":40,"props":852,"children":853},{},[854],{"type":37,"value":855},"channel contribution",{"type":37,"value":857}," table. It shows each channel's revenue contribution percentage and ",{"type":32,"tag":40,"props":859,"children":860},{},[861],{"type":37,"value":862},"ROAS",{"type":37,"value":864}," (return on ad spend). But beware: these are historical ROAS values, not ",{"type":32,"tag":40,"props":866,"children":867},{},[868],{"type":37,"value":869},"marginal ROAS",{"type":37,"value":871},". Marginal ROAS reveals the additional revenue your next 1,000 spent will generate, calculated as the derivative of the saturation curve.",{"type":32,"tag":33,"props":873,"children":874},{},[875,877,883],{"type":37,"value":876},"Robyn's ",{"type":32,"tag":122,"props":878,"children":880},{"className":879},[],[881],{"type":37,"value":882},"budget_allocator()",{"type":37,"value":884}," function redistributes your current budget according to saturation curves. If Google Search saturates, excess budget shifts to Meta or TikTok. This optimization finds the point where marginal returns equalize across channels (Economics 101: MR₁ = MR₂).",{"type":32,"tag":115,"props":886,"children":888},{"code":887,"language":118,"meta":16,"className":119,"style":16},"AllocatorCollect \u003C- robyn_allocator(\n  robyn_object = OutputModels,\n  select_model = \"1_100_2\", # Model ID from Pareto frontier\n  scenario = \"max_response_expected_spend\",\n  channel_constr_low = c(0.7, 0.7, 0.5),   # Min 70% Google, 70% Meta, 50% TikTok\n  channel_constr_up = c(1.5, 2.0, 3.0),    # Max increase caps\n  expected_spend = 100000\n)\n",[889],{"type":32,"tag":122,"props":890,"children":891},{"__ignoreMap":16},[892,900,907,915,923,931,939,947],{"type":32,"tag":126,"props":893,"children":894},{"class":128,"line":129},[895],{"type":32,"tag":126,"props":896,"children":897},{},[898],{"type":37,"value":899},"AllocatorCollect \u003C- robyn_allocator(\n",{"type":32,"tag":126,"props":901,"children":902},{"class":128,"line":138},[903],{"type":32,"tag":126,"props":904,"children":905},{},[906],{"type":37,"value":803},{"type":32,"tag":126,"props":908,"children":909},{"class":128,"line":147},[910],{"type":32,"tag":126,"props":911,"children":912},{},[913],{"type":37,"value":914},"  select_model = \"1_100_2\", # Model ID from Pareto frontier\n",{"type":32,"tag":126,"props":916,"children":917},{"class":128,"line":156},[918],{"type":32,"tag":126,"props":919,"children":920},{},[921],{"type":37,"value":922},"  scenario = \"max_response_expected_spend\",\n",{"type":32,"tag":126,"props":924,"children":925},{"class":128,"line":165},[926],{"type":32,"tag":126,"props":927,"children":928},{},[929],{"type":37,"value":930},"  channel_constr_low = c(0.7, 0.7, 0.5),   # Min 70% Google, 70% Meta, 50% TikTok\n",{"type":32,"tag":126,"props":932,"children":933},{"class":128,"line":174},[934],{"type":32,"tag":126,"props":935,"children":936},{},[937],{"type":37,"value":938},"  channel_constr_up = c(1.5, 2.0, 3.0),    # Max increase caps\n",{"type":32,"tag":126,"props":940,"children":941},{"class":128,"line":183},[942],{"type":32,"tag":126,"props":943,"children":944},{},[945],{"type":37,"value":946},"  expected_spend = 100000\n",{"type":32,"tag":126,"props":948,"children":949},{"class":128,"line":26},[950],{"type":32,"tag":126,"props":951,"children":952},{},[953],{"type":37,"value":224},{"type":32,"tag":33,"props":955,"children":956},{},[957,959,964],{"type":37,"value":958},"The output shows how to allocate your current 100,000 spend for optimal revenue. But this is static guidance—real life shifts with creative refresh, competitor moves, and seasonality. Refresh MMM ",{"type":32,"tag":40,"props":960,"children":961},{},[962],{"type":37,"value":963},"monthly",{"type":37,"value":965},".",{"type":32,"tag":48,"props":967,"children":969},{"id":968},"tradeoffs-and-limitations",[970],{"type":37,"value":971},"Tradeoffs and limitations",{"type":32,"tag":33,"props":973,"children":974},{},[975,977,982,984,989],{"type":37,"value":976},"Unlike attribution, MMM works at ",{"type":32,"tag":40,"props":978,"children":979},{},[980],{"type":37,"value":981},"aggregate level",{"type":37,"value":983},". This means it can't inform personalization. Robyn won't tell you which keywords drive better performance in Google Search—only the total Search channel's contribution. The model is also vulnerable to ",{"type":32,"tag":40,"props":985,"children":986},{},[987],{"type":37,"value":988},"correlation ≠ causation",{"type":37,"value":990},": if sales spike in summer and you increase TV spend in summer, the model may overweight TV's credit.",{"type":32,"tag":33,"props":992,"children":993},{},[994,996,1001,1003,1009],{"type":37,"value":995},"Solve this by validating MMM with ",{"type":32,"tag":40,"props":997,"children":998},{},[999],{"type":37,"value":1000},"incrementality tests",{"type":37,"value":1002},". Measure true causal impact via geo-lift or holdout tests, then compare to MMM results. Robyn accepts incrementality findings as ",{"type":32,"tag":122,"props":1004,"children":1006},{"className":1005},[],[1007],{"type":37,"value":1008},"calibration",{"type":37,"value":1010}," parameters—they act as Bayesian priors, anchoring the model to ground truth.",{"type":32,"tag":33,"props":1012,"children":1013},{},[1014,1016,1021],{"type":37,"value":1015},"Adding ",{"type":32,"tag":40,"props":1017,"children":1018},{},[1019],{"type":37,"value":1020},"new channels",{"type":37,"value":1022}," presents challenges. If you launch a new channel (say, Snapchat) with only eight weeks of data, Robyn can't learn its saturation curve. Either set manual priors or exclude the channel's first 12 weeks, adding it later.",{"type":32,"tag":33,"props":1024,"children":1025},{},[1026,1028,1033],{"type":37,"value":1027},"Finally, MMM grows strongest when ",{"type":32,"tag":40,"props":1029,"children":1030},{},[1031],{"type":37,"value":1032},"uniting offline and online",{"type":37,"value":1034},". Without offline channels (TV, outdoor, sponsorships) in the model, it overweights online channels (omitted variable bias). Robyn flexes here: it accepts GRP, reach, even brand search volume as proxies.",{"type":32,"tag":33,"props":1036,"children":1037},{},[1038],{"type":37,"value":1039},"A correctly built MMM pipeline transforms marketing budget planning from guesswork to evidence-based engineering. Robyn makes this shift accessible via open source—but data structure, hyperparameter tuning, and incrementality validation demand human expertise. Teams investing in econometric regression over attribution in the cookie-free era will be 12 months ahead of rivals by 2027.",{"type":32,"tag":1041,"props":1042,"children":1043},"style",{},[1044],{"type":37,"value":1045},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":16,"searchDepth":147,"depth":147,"links":1047},[1048,1049,1050,1051,1052,1053],{"id":50,"depth":138,"text":53},{"id":105,"depth":138,"text":108},{"id":363,"depth":138,"text":366},{"id":539,"depth":138,"text":542},{"id":842,"depth":138,"text":845},{"id":968,"depth":138,"text":971},"markdown","content:en:data:marketing-mix-modeling-practical-setup-with-robyn.md","content","en\u002Fdata\u002Fmarketing-mix-modeling-practical-setup-with-robyn.md","en\u002Fdata\u002Fmarketing-mix-modeling-practical-setup-with-robyn","md",1782079488201]