do-attribution 0.0.2__tar.gz → 0.0.3__tar.gz
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.
- {do_attribution-0.0.2/do_attribution.egg-info → do_attribution-0.0.3}/PKG-INFO +317 -77
- {do_attribution-0.0.2 → do_attribution-0.0.3}/README.md +313 -72
- {do_attribution-0.0.2 → do_attribution-0.0.3}/do_attribution/__init__.py +5 -0
- do_attribution-0.0.3/do_attribution/_static_export.py +128 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/do_attribution/_temporal_helpers.py +76 -2
- do_attribution-0.0.3/do_attribution/baselines.py +192 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/do_attribution/calibration.py +290 -46
- {do_attribution-0.0.2 → do_attribution-0.0.3}/do_attribution/compare.py +134 -4
- {do_attribution-0.0.2 → do_attribution-0.0.3}/do_attribution/markov.py +114 -33
- {do_attribution-0.0.2 → do_attribution-0.0.3}/do_attribution/reporting.py +77 -7
- do_attribution-0.0.3/do_attribution/shapley.py +668 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/do_attribution/temporal_markov.py +54 -67
- {do_attribution-0.0.2 → do_attribution-0.0.3}/do_attribution/temporal_shapley.py +91 -81
- do_attribution-0.0.3/do_attribution/utils.py +571 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3/do_attribution.egg-info}/PKG-INFO +317 -77
- {do_attribution-0.0.2 → do_attribution-0.0.3}/do_attribution.egg-info/SOURCES.txt +10 -2
- {do_attribution-0.0.2 → do_attribution-0.0.3}/do_attribution.egg-info/requires.txt +2 -2
- {do_attribution-0.0.2 → do_attribution-0.0.3}/docs/calibration_diagnostics.md +55 -5
- do_attribution-0.0.3/examples/attribution_experiment_calibration.ipynb +3327 -0
- do_attribution-0.0.3/examples/basic_markov.ipynb +716 -0
- do_attribution-0.0.3/examples/basic_shapley.ipynb +807 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/examples/calibration_workflow.py +330 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/examples/generate_calibration_visuals.py +17 -21
- do_attribution-0.0.3/examples/images/calibration_experimental_evidence.png +0 -0
- do_attribution-0.0.3/examples/images/calibration_static_effects.png +0 -0
- do_attribution-0.0.3/examples/images/calibration_temporal_effects.png +0 -0
- do_attribution-0.0.3/examples/images/calibration_temporal_entity_mix.png +0 -0
- do_attribution-0.0.3/examples/images/calibration_temporal_evidence_uncertainty.png +0 -0
- do_attribution-0.0.3/examples/images/calibration_temporal_residual_allocation.png +0 -0
- do_attribution-0.0.3/examples/images/ga4_model_comparison.png +0 -0
- do_attribution-0.0.3/examples/images/ga4_shapley_attribution.png +0 -0
- do_attribution-0.0.3/examples/images/ga4_temporal_markov.png +0 -0
- do_attribution-0.0.3/examples/images/ga4_temporal_shapley.png +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/examples/methodological_stress_tests.ipynb +116 -63
- do_attribution-0.0.3/examples/side_by_side.ipynb +533 -0
- do_attribution-0.0.3/examples/temporal_markov.ipynb +1486 -0
- do_attribution-0.0.3/examples/temporal_shapley.ipynb +1889 -0
- do_attribution-0.0.3/examples/weighted_attribution.ipynb +1416 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/pyproject.toml +4 -6
- do_attribution-0.0.3/tests/test_baselines.py +346 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_calibration_cross_cutting_guarantees.py +1 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_calibration_docs_diagnostics.py +5 -4
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_calibration_repaired_contracts.py +3 -1
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_calibration_reporting_gate.py +1 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_calibration_shared_gate.py +1 -0
- do_attribution-0.0.3/tests/test_calibration_standard_error_contract.py +232 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_calibration_summary.py +64 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_calibration_visual_artifacts.py +25 -3
- do_attribution-0.0.3/tests/test_calibration_workflow_example.py +631 -0
- do_attribution-0.0.3/tests/test_compare.py +754 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_installed_package.py +7 -1
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_markov.py +142 -4
- do_attribution-0.0.3/tests/test_notebook_contracts.py +286 -0
- do_attribution-0.0.3/tests/test_readme_examples.py +182 -0
- do_attribution-0.0.3/tests/test_shapley.py +1138 -0
- do_attribution-0.0.3/tests/test_temporal_annual_frequencies.py +100 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_calibration_summary.py +51 -0
- do_attribution-0.0.3/tests/test_temporal_cross_year_epochs.py +267 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_entity_mix_plot.py +28 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_epoch_alignment_plot.py +50 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_evidence_export.py +84 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_evidence_strength_plot.py +143 -15
- do_attribution-0.0.3/tests/test_temporal_evidence_uncertainty_plot.py +298 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_markov.py +53 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_ratio_schedule_plot.py +153 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_shapley.py +172 -5
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_v2_mixed_freq.py +3 -1
- do_attribution-0.0.3/tests/test_to_dataframes.py +1125 -0
- do_attribution-0.0.3/tests/test_utils.py +613 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_visualization.py +27 -0
- do_attribution-0.0.2/do_attribution/shapley.py +0 -304
- do_attribution-0.0.2/do_attribution/utils.py +0 -299
- do_attribution-0.0.2/examples/attribution_experiment_calibration.ipynb +0 -3219
- do_attribution-0.0.2/examples/basic_markov.ipynb +0 -749
- do_attribution-0.0.2/examples/basic_shapley.ipynb +0 -784
- do_attribution-0.0.2/examples/images/calibration_static_effects.png +0 -0
- do_attribution-0.0.2/examples/images/calibration_temporal_effects.png +0 -0
- do_attribution-0.0.2/examples/images/calibration_temporal_entity_mix.png +0 -0
- do_attribution-0.0.2/examples/images/calibration_temporal_evidence_strength.png +0 -0
- do_attribution-0.0.2/examples/images/calibration_temporal_ratio_schedule.png +0 -0
- do_attribution-0.0.2/examples/images/calibration_temporal_residual_allocation.png +0 -0
- do_attribution-0.0.2/examples/images/ga4_model_comparison.png +0 -0
- do_attribution-0.0.2/examples/images/ga4_shapley_attribution.png +0 -0
- do_attribution-0.0.2/examples/images/ga4_temporal_markov.png +0 -0
- do_attribution-0.0.2/examples/images/ga4_temporal_shapley.png +0 -0
- do_attribution-0.0.2/examples/side_by_side.ipynb +0 -626
- do_attribution-0.0.2/examples/temporal_markov.ipynb +0 -1540
- do_attribution-0.0.2/examples/temporal_shapley.ipynb +0 -1551
- do_attribution-0.0.2/tests/test_calibration_standard_error_contract.py +0 -56
- do_attribution-0.0.2/tests/test_calibration_workflow_example.py +0 -258
- do_attribution-0.0.2/tests/test_compare.py +0 -141
- do_attribution-0.0.2/tests/test_notebook_contracts.py +0 -104
- do_attribution-0.0.2/tests/test_shapley.py +0 -403
- do_attribution-0.0.2/tests/test_to_dataframes.py +0 -434
- do_attribution-0.0.2/tests/test_utils.py +0 -236
- {do_attribution-0.0.2 → do_attribution-0.0.3}/LICENSE +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/MANIFEST.in +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/do_attribution/_calibration_validation.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/do_attribution/base.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/do_attribution/normalization.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/do_attribution/temporal.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/do_attribution/temporal_diagnostics.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/do_attribution/visualization.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/do_attribution.egg-info/dependency_links.txt +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/do_attribution.egg-info/top_level.txt +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/docs/calibration.md +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/docs/calibration_examples.md +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/docs/calibration_examples_conflicts.md +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/docs/calibration_examples_schedules.md +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/docs/calibration_examples_temporal.md +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/examples/ga4_attribution_journeys.csv +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/examples/images/calibration_temporal_epoch_alignment.png +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/examples/images/ga4_coalition_values.png +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/examples/images/ga4_markov_attribution.png +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/examples/images/ga4_markov_epoch_recommendation.png +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/examples/images/ga4_markov_removal_effects.png +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/examples/images/ga4_shapley_epoch_recommendation.png +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/examples/images/ga4_transition_graph.png +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/requirements.txt +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/setup.cfg +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/setup.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/__init__.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/conftest.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/fixtures/calibration_api_contract.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_calibration_api_contract.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_calibration_contract.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_calibration_dataframe_export.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_calibration_docs_conflict_examples.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_calibration_docs_schedule_examples.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_calibration_docs_static_examples.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_calibration_docs_temporal_examples.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_calibration_evidence_collection.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_calibration_normalization.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_calibration_numeric_validation.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_calibration_package_exports.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_calibration_policy_contract.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_calibration_results.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_calibration_row.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_calibration_skeletons.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_calibration_validation_boundary.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_epoch_ratio_row.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_evidence_point_record.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_lift_test_evidence.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_normalization.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_package_metadata.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_ratio_schedule_row.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_static_allocation_feasibility.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_static_allow_total_change.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_static_calibration_effects_plot.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_static_calibration_properties.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_static_custom_weights.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_static_direct_adjustment.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_static_explicit_adjustments.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_static_generated_properties.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_static_markov_calibration_integration.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_static_multiple_tested_entities.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_static_proportional_untested.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_static_residual_bucket.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_static_residual_dispatch.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_static_residual_policy_matrix.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_static_shapley_calibration_integration.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_static_share_equivalence.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_allocation_provenance_export.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_allow_total_change.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_apply_estimates.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_calibration_construction.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_calibration_effects_plot.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_constant_schedule.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_coordinate_average.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_coordinate_linear_schedule.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_coordinate_normalization.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_coordinate_schedule_export.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_custom_weights.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_diagnostics.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_direct_epoch_ratio.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_epoch_coordinate_weights.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_epoch_coordinates.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_epoch_metadata_gate.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_epoch_ratio_dispatch.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_epoch_ratio_export.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_epoch_ratio_gate.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_epoch_support.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_epoch_windows.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_evidence_conflict_dispatch.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_evidence_conflict_gate.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_explicit_adjustments.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_explicit_ratio_schedule.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_explicit_strength_schedule.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_generated_properties.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_helper_equivalence.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_markov_calibration_integration.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_observed_only_schedule.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_pooled_lift.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_proportional_untested.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_ratio_policy_dispatch.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_ratio_schedule_gate.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_recommendation.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_reporting.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_representative_coordinate.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_residual_allocation_gate.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_residual_allocation_plot.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_residual_bucket.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_residual_policy_dispatch.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_select_evidence.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_shapley_calibration_integration.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_share_equivalence.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_support_weighted_average.py +0 -0
- {do_attribution-0.0.2 → do_attribution-0.0.3}/tests/test_temporal_weighted_ratio.py +0 -0
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: do-attribution
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3
|
|
4
4
|
Summary: Unified multi-touch attribution library (Markov chain + Shapley value).
|
|
5
5
|
Author-email: Aaron Goodin <goodinaaron@causalscience.com>
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Keywords: attribution,marketing,markov,shapley,multi-touch,analytics,incrementality
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
9
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
10
9
|
Classifier: Programming Language :: Python :: 3.11
|
|
11
10
|
Classifier: Programming Language :: Python :: 3.12
|
|
12
11
|
Classifier: Operating System :: OS Independent
|
|
13
12
|
Classifier: Intended Audience :: Science/Research
|
|
14
13
|
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
15
|
-
Requires-Python: >=3.
|
|
14
|
+
Requires-Python: >=3.11
|
|
16
15
|
Description-Content-Type: text/markdown
|
|
17
16
|
License-File: LICENSE
|
|
18
|
-
Requires-Dist: numpy>=
|
|
19
|
-
Requires-Dist: pandas>=1.
|
|
17
|
+
Requires-Dist: numpy>=2.0.2
|
|
18
|
+
Requires-Dist: pandas>=3.1.1
|
|
20
19
|
Provides-Extra: viz
|
|
21
20
|
Requires-Dist: matplotlib>=3.3.0; extra == "viz"
|
|
22
21
|
Requires-Dist: networkx>=2.5; extra == "viz"
|
|
@@ -41,7 +40,7 @@ closed-form Markov, exact Shapley, temporal attribution, and explicit
|
|
|
41
40
|
calibration from external lift-test evidence.**
|
|
42
41
|
|
|
43
42
|

|
|
44
|
-

|
|
45
44
|
|
|
46
45
|
`do-attribution` has two explicit layers:
|
|
47
46
|
|
|
@@ -59,7 +58,7 @@ allocation models:
|
|
|
59
58
|
- an **absorbing-Markov-chain removal-effect** model
|
|
60
59
|
(`MarkovAttribution`, orders 1+, four removal strategies), and
|
|
61
60
|
- a **cooperative-game Shapley-value** model (`ShapleyAttribution`, exact
|
|
62
|
-
|
|
61
|
+
Shapley values of a coalition game built from the observed channel sets).
|
|
63
62
|
|
|
64
63
|
The calibration layer applies caller-supplied lift-test evidence after a
|
|
65
64
|
static or temporal attribution fit. Direct adjustments to tested entities,
|
|
@@ -94,11 +93,22 @@ chronological epochs.
|
|
|
94
93
|
`"fail_refit"` (signed effects), `"detour"` (signed effects), or
|
|
95
94
|
`"redirect_to_null"` — four operational meanings of "remove this channel",
|
|
96
95
|
selected explicitly instead of implied.
|
|
97
|
-
- **Exact Shapley values.**
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
96
|
+
- **Exact Shapley values.** The default cumulative coalition game credits a
|
|
97
|
+
coalition with every conversion on journeys whose channels it contains, so
|
|
98
|
+
the Shapley values are a non-negative allocation of all observed
|
|
99
|
+
conversions. They are exact — no sampling — and equal the factorial-weight
|
|
100
|
+
definition `w(s, n) = s!·(n−s−1)!/n!`. Raw values and the observed
|
|
101
|
+
coalition table are always returned; normalized shares are returned when
|
|
102
|
+
the raw values admit a valid share vector, and reported as unavailable
|
|
103
|
+
when they do not. The legacy exact-set game remains available through
|
|
104
|
+
`characteristic_function="exact"`.
|
|
105
|
+
- **Optional observation weights.** `ShapleyAttribution.fit` and
|
|
106
|
+
`CompareAttribution.fit` accept per-journey `weights=` — frequency counts,
|
|
107
|
+
sampling weights, or any other nonnegative weight — under the same rules
|
|
108
|
+
as `MarkovAttribution.fit`. Weighted coalition totals are kept as
|
|
109
|
+
fractions, zero-weight journeys are excluded, and a weighted comparison
|
|
110
|
+
fits Markov and Shapley on the same weighted journeys. A fit without
|
|
111
|
+
weights is unchanged.
|
|
102
112
|
- **Conservation and contract guarantees.** When normalized attribution is
|
|
103
113
|
available its shares are finite, lie on `[0, 1]`, and sum to 1, and
|
|
104
114
|
attributed conversions conserve the observed total. When it is not,
|
|
@@ -110,10 +120,10 @@ chronological epochs.
|
|
|
110
120
|
or a natural-language `summary(output_format="report")` — plain Python
|
|
111
121
|
strings with bounded, distilled detail tables (signed removal effects, top
|
|
112
122
|
transitions and drop-offs, top coalitions, path diagnostics).
|
|
113
|
-
- **Flat DataFrame export.**
|
|
123
|
+
- **Flat DataFrame export.** Static and temporal models expose
|
|
114
124
|
`to_dataframes() -> dict[str, pd.DataFrame]`: flat, string-keyed,
|
|
115
|
-
Parquet/SQL-ready tables for attribution, raw model values, and
|
|
116
|
-
diagnostics — no text parsing, no `MultiIndex`, no lazy refits.
|
|
125
|
+
Parquet/SQL-ready tables for attribution, raw model values, and fit or
|
|
126
|
+
sample diagnostics — no text parsing, no `MultiIndex`, no lazy refits.
|
|
117
127
|
- **Visualization.** `plot_attribution`, `plot_removal_effects`,
|
|
118
128
|
`plot_transition_graph`, `plot_coalition_values`,
|
|
119
129
|
`plot_attribution_comparison`, and `plot_epoch_recommendation` each return
|
|
@@ -127,8 +137,9 @@ chronological epochs.
|
|
|
127
137
|
adjustments, residual allocation, evidence schedules, epoch-level
|
|
128
138
|
reductions, and provenance explicitly separate.
|
|
129
139
|
- **Calibration diagnostics.** One static adjustment-decomposition plot and
|
|
130
|
-
|
|
131
|
-
|
|
140
|
+
seven temporal diagnostics show evidence alignment, ratio construction,
|
|
141
|
+
experiment-reported evidence uncertainty, direct adjustments, residual
|
|
142
|
+
allocation, entity mix, and calibration-policy comparisons.
|
|
132
143
|
- **Reproducible outputs.** Deterministic model fits, deterministic
|
|
133
144
|
renderers, and deterministic tie ordering — identical inputs always
|
|
134
145
|
produce identical reports, tables, and figures.
|
|
@@ -155,7 +166,7 @@ pip install "do-attribution[viz]"
|
|
|
155
166
|
pip install "do-attribution[temporal,viz]"
|
|
156
167
|
```
|
|
157
168
|
|
|
158
|
-
Requires Python
|
|
169
|
+
Requires Python 3.11 or newer, NumPy 2.0.2 or newer, and pandas 3.1.1 or newer.
|
|
159
170
|
|
|
160
171
|
---
|
|
161
172
|
|
|
@@ -223,30 +234,47 @@ Removal effects (raw, signed) vs normalized shares
|
|
|
223
234
|
+---------+----------------+--------+
|
|
224
235
|
```
|
|
225
236
|
|
|
226
|
-
The Shapley model is a drop-in alternative on the same inputs
|
|
227
|
-
`attribution` may be `None` — always check before indexing it:
|
|
237
|
+
The Shapley model is a drop-in alternative on the same inputs:
|
|
228
238
|
|
|
229
239
|
```python
|
|
230
240
|
from do_attribution import ShapleyAttribution
|
|
231
241
|
|
|
232
242
|
shapley = ShapleyAttribution(conversion_labels=["Purchase"]).fit(journeys, conversions)
|
|
233
243
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
print({ch: round(share, 4) for ch, share in shapley["attribution"].items()})
|
|
244
|
+
print(shapley["normalization_status"])
|
|
245
|
+
# available
|
|
246
|
+
print({ch: round(share, 4) for ch, share in shapley["attribution"].items()})
|
|
247
|
+
# {'Direct': 0.1429, 'Email': 0.3095, 'Search': 0.1667, 'Social': 0.381}
|
|
248
|
+
print({ch: round(v, 4) for ch, v in shapley["shapley_values"].items()})
|
|
249
|
+
# {'Direct': 1.0, 'Email': 2.1667, 'Search': 1.1667, 'Social': 2.6667}
|
|
241
250
|
```
|
|
242
251
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
252
|
+
Each converting journey splits its conversion equally among the distinct
|
|
253
|
+
channels it touched, so the raw values sum to the seven observed conversions
|
|
254
|
+
and the shares are those values divided by seven. `attribution` is `None`
|
|
255
|
+
when normalization is unavailable — for the default game, only when no
|
|
256
|
+
conversion can be credited to a channel — so code that must handle that case
|
|
257
|
+
should check `normalization_status` before indexing it.
|
|
247
258
|
|
|
248
259
|
Lists, NumPy arrays, and pandas Series all work as path and conversion
|
|
249
|
-
inputs.
|
|
260
|
+
inputs. Both must be one-dimensional: a bare string, a scalar, or a nested or
|
|
261
|
+
two-dimensional container raises instead of being read as a collection of
|
|
262
|
+
journeys. Each conversion outcome must be exactly `0` or `1`. Integers,
|
|
263
|
+
booleans, and the exact numeric values `0.0` / `1.0` are accepted in any of
|
|
264
|
+
those containers. The canonical strings `"0"` and `"1"` (text or bytes)
|
|
265
|
+
remain accepted as well; no other spelling (`" 1"`, `"01"`, `"1.0"`,
|
|
266
|
+
`"true"`) is. Every `fit` entry point — static, temporal (`fit` and
|
|
267
|
+
`fit_by_epoch`), and `CompareAttribution` — and the `validate_data` /
|
|
268
|
+
`to_int_array` helpers check the supplied values before integer conversion
|
|
269
|
+
and raise a `ValueError` naming the position and the original value for a
|
|
270
|
+
fractional, out-of-range, missing (`None`, `NaN`, `pd.NA`), or non-finite
|
|
271
|
+
outcome.
|
|
272
|
+
|
|
273
|
+
> **Compatibility — binary outcomes:** this enforces the existing
|
|
274
|
+
> binary-outcome contract. Earlier releases cast outcomes to integers before
|
|
275
|
+
> checking them, so `[0.9, 1.9]` was silently fitted as `[0, 1]`. Callers
|
|
276
|
+
> passing probabilities or fractional counts must supply valid `0`/`1`
|
|
277
|
+
> journey outcomes rather than relying on truncation.
|
|
250
278
|
|
|
251
279
|
---
|
|
252
280
|
|
|
@@ -263,6 +291,19 @@ identifies candidate temporal boundaries.
|
|
|
263
291
|
| <img src="https://raw.githubusercontent.com/8139CAUSAL/do_attribution/main/examples/images/ga4_model_comparison.png" width="420" alt="GA4 Markov vs Shapley comparison chart"> | <img src="https://raw.githubusercontent.com/8139CAUSAL/do_attribution/main/examples/images/ga4_markov_epoch_recommendation.png" width="420" alt="GA4 epoch recommendation diagnostic plot"> |
|
|
264
292
|
| `plot_attribution_comparison` — Markov and Shapley attribution on the same journeys | `plot_epoch_recommendation` — candidate and retained epoch boundaries on the conversion-rate signal |
|
|
265
293
|
|
|
294
|
+
The calibration example starts from the experiment. One figure follows the
|
|
295
|
+
randomized outcomes into the calibration: what each arm converted, the
|
|
296
|
+
estimated lift with its 95% confidence interval beside the matched attribution
|
|
297
|
+
estimate, and the calibration ratio of each epoch. The December ratio is
|
|
298
|
+
marked as interpolated because no experiment ran in that month. The interval
|
|
299
|
+
belongs to the experimental lift only; calibration does not propagate the
|
|
300
|
+
standard error, so no ratio and no calibrated estimate carries one.
|
|
301
|
+
|
|
302
|
+
| |
|
|
303
|
+
| :---: |
|
|
304
|
+
| <img src="https://raw.githubusercontent.com/8139CAUSAL/do_attribution/main/examples/images/calibration_experimental_evidence.png" width="860" alt="Experimental lift evidence and its translation into attribution calibration: arm outcomes, lift with 95% confidence interval versus matched MTA estimate, and calibration ratios across time"> |
|
|
305
|
+
| `plot_experimental_evidence` in `examples/calibration_workflow.py` — randomized outcomes, estimated lift versus matched attribution, and observed versus interpolated calibration ratios |
|
|
306
|
+
|
|
266
307
|
Calibration diagnostics keep experimental evidence, constructed calibration
|
|
267
308
|
ratios, direct adjustments, and policy-based residual allocation visually
|
|
268
309
|
distinct.
|
|
@@ -271,8 +312,8 @@ distinct.
|
|
|
271
312
|
| :---: | :---: |
|
|
272
313
|
| <img src="https://raw.githubusercontent.com/8139CAUSAL/do_attribution/main/examples/images/calibration_static_effects.png" width="420" alt="Static calibration effects"> | <img src="https://raw.githubusercontent.com/8139CAUSAL/do_attribution/main/examples/images/calibration_temporal_epoch_alignment.png" width="420" alt="Temporal epoch and experiment alignment"> |
|
|
273
314
|
| Decomposes the calibrated estimate into original attribution, direct test adjustment, and residual allocation | Shows where experimental evidence falls on caller-defined epochs without redefining the epoch boundaries |
|
|
274
|
-
| <img src="https://raw.githubusercontent.com/8139CAUSAL/do_attribution/main/examples/images/
|
|
275
|
-
|
|
|
315
|
+
| <img src="https://raw.githubusercontent.com/8139CAUSAL/do_attribution/main/examples/images/calibration_temporal_residual_allocation.png" width="420" alt="Temporal residual allocation"> | |
|
|
316
|
+
| Shows the required residual adjustment and its policy-based allocation across eligible recipients | |
|
|
276
317
|
|
|
277
318
|
---
|
|
278
319
|
|
|
@@ -295,6 +336,28 @@ residual allocation, or calibrated total.
|
|
|
295
336
|
Calibrated point outputs therefore do not supply confidence intervals,
|
|
296
337
|
posterior uncertainty, or uncertainty-adjusted calibration ratios.
|
|
297
338
|
|
|
339
|
+
The standard error is displayed wherever the evidence itself is displayed,
|
|
340
|
+
as the experiment's reported uncertainty about its own observed lift:
|
|
341
|
+
|
|
342
|
+
- `summary()` (static and temporal) prints an
|
|
343
|
+
`Observed lift ± standard error` line under each evidence record, for
|
|
344
|
+
example `360.00 ± 25.00 (standard error reported by the experiment;
|
|
345
|
+
calibration does not use it)`. A record without one prints
|
|
346
|
+
`360.00; no uncertainty supplied`.
|
|
347
|
+
- The temporal `to_dataframes()["evidence"]` table carries `observed_lift`,
|
|
348
|
+
`standard_error`, and `uncertainty_status` beside each record;
|
|
349
|
+
`uncertainty_status` reads `standard error reported by the experiment` or
|
|
350
|
+
`no uncertainty supplied`, so a missing value is never an implied zero.
|
|
351
|
+
- `plot_evidence_uncertainty()` draws each evidence record's observed lift
|
|
352
|
+
on a lift axis with a horizontal ±1 standard-error bar, and labels a record
|
|
353
|
+
without one `no uncertainty supplied`. `plot_ratio_schedule()` draws the
|
|
354
|
+
same reported error on its observed anchors, divided by the matched
|
|
355
|
+
attribution estimate so it sits on the ratio axis. Constructed schedule
|
|
356
|
+
values, effective ratios, and calibrated estimates never carry a bar.
|
|
357
|
+
|
|
358
|
+
Changing `standard_error` changes those annotations only; every calibrated
|
|
359
|
+
number, schedule, and provenance record is identical.
|
|
360
|
+
|
|
298
361
|
`AttributionCalibration` applies that evidence to caller-approved static
|
|
299
362
|
attribution estimates. `TemporalAttributionCalibration` applies dated
|
|
300
363
|
evidence to estimates on a caller-supplied, authoritative epoch grid; the
|
|
@@ -315,15 +378,15 @@ the calibration notebook:
|
|
|
315
378
|
from do_attribution import AttributionCalibration, LiftTestEvidence
|
|
316
379
|
|
|
317
380
|
attributed_conversions = {
|
|
318
|
-
"Experimental Media":
|
|
319
|
-
"
|
|
320
|
-
"
|
|
321
|
-
"Direct":
|
|
322
|
-
"Other":
|
|
323
|
-
"Paid Search":
|
|
324
|
-
"
|
|
325
|
-
"
|
|
326
|
-
"
|
|
381
|
+
"Experimental Media": 1037.8436,
|
|
382
|
+
"Referral": 912.5517,
|
|
383
|
+
"Organic Search": 834.4344,
|
|
384
|
+
"Direct": 391.0936,
|
|
385
|
+
"Other": 198.3367,
|
|
386
|
+
"Paid Search": 40.5804,
|
|
387
|
+
"Email": 7.1016,
|
|
388
|
+
"Organic Social": 2.5363,
|
|
389
|
+
"Affiliate": 1.5218,
|
|
327
390
|
}
|
|
328
391
|
|
|
329
392
|
evidence = LiftTestEvidence(
|
|
@@ -412,12 +475,27 @@ legacy_refit["population_diagnostics"]["A"]["transformed_journey_count"] # 4
|
|
|
412
475
|
Raw effects from `fail`, `fail_refit`, and `detour` are diagnostics rather
|
|
413
476
|
than probabilities. Signed effects from `fail_refit` or `detour` do not
|
|
414
477
|
necessarily define a probability distribution and must not be interpreted as
|
|
415
|
-
percentages.
|
|
416
|
-
shared
|
|
417
|
-
|
|
478
|
+
percentages. Every strategy, `redirect_to_null` included, normalizes its raw
|
|
479
|
+
effects through the same shared contract, and only when they satisfy its
|
|
480
|
+
finite, nonnegative, bounded, unit-sum rules. `fit` always returns
|
|
418
481
|
`normalization_status`, `normalization_reason`, and the raw-sum, magnitude,
|
|
419
|
-
denominator, and cancellation-ratio diagnostics
|
|
420
|
-
|
|
482
|
+
denominator, and cancellation-ratio diagnostics. If signed effects cancel at
|
|
483
|
+
or below the scale-aware tolerance, or every effect is zero (no journey
|
|
484
|
+
converts, or stripping `conversion_labels` leaves no converting journey with
|
|
485
|
+
a channel), `attribution` is `None` and those diagnostics explain why while
|
|
486
|
+
`removal_effects` and `total_conversion_probability` remain available.
|
|
487
|
+
|
|
488
|
+
> **Compatibility — `redirect_to_null`:** earlier releases substituted an
|
|
489
|
+
> equal share per channel, with a `Total removal effect is 0` warning, when
|
|
490
|
+
> every removal effect was zero, and did not return the normalization
|
|
491
|
+
> diagnostics. Such a fit now returns `attribution=None` with
|
|
492
|
+
> `normalization_reason="zero_raw_magnitude"` and no warning; temporal
|
|
493
|
+
> epochs and `CompareAttribution` columns built from it hold missing shares
|
|
494
|
+
> instead of equal ones. Callers that assumed `redirect_to_null` always
|
|
495
|
+
> returned a dictionary need the availability check the other strategies
|
|
496
|
+
> already require: test `normalization_status` (or `attribution is None`)
|
|
497
|
+
> before indexing. Removal-effect mathematics, and the shares of any fit
|
|
498
|
+
> with a nonzero effect, are unchanged.
|
|
421
499
|
|
|
422
500
|
Higher orders (`order=2`, `order=3`, …) use sliding tuples as states; short
|
|
423
501
|
paths are left-padded with `START`. `fit` returns `attribution`,
|
|
@@ -426,36 +504,64 @@ paths are left-padded with `START`. `fit` returns `attribution`,
|
|
|
426
504
|
|
|
427
505
|
### Shapley coalition attribution
|
|
428
506
|
|
|
429
|
-
`ShapleyAttribution`
|
|
430
|
-
whose unique channel set is exactly
|
|
431
|
-
|
|
432
|
-
`
|
|
507
|
+
`ShapleyAttribution` builds a coalition game from the observed coalition
|
|
508
|
+
table *R(T) = conversions on paths whose unique channel set is exactly T*,
|
|
509
|
+
which `fit` returns as `value_function` alongside `attribution` and the raw
|
|
510
|
+
`shapley_values`:
|
|
433
511
|
|
|
434
512
|
```python
|
|
435
513
|
shapley["value_function"]
|
|
436
514
|
# {('Email', 'Social'): 2, ('Email', 'Search'): 1, ('Social',): 1, ...}
|
|
437
515
|
```
|
|
438
516
|
|
|
439
|
-
|
|
440
|
-
|
|
517
|
+
`characteristic_function=` selects the game *v* the Shapley values are
|
|
518
|
+
computed on:
|
|
519
|
+
|
|
520
|
+
| Game | *v(S)* | Efficiency: the raw values sum to | Raw values |
|
|
521
|
+
| --- | --- | --- | --- |
|
|
522
|
+
| `"cumulative"` (default) | Conversions on paths whose channel set is contained in *S*: the sum of *R(T)* over observed *T ⊆ S*. | Every observed conversion. | Non-negative. Each path splits its conversion equally among its distinct channels: `phi_i = Σ R(T) / |T|` over the coalitions *T* containing *i*. |
|
|
523
|
+
| `"exact"` (legacy) | *R(S)*: conversions on paths whose channel set is exactly *S*. | The conversions on paths that touch every channel, `v(N)`. | Signed. |
|
|
524
|
+
|
|
525
|
+
The cumulative game is monotone and its grand coalition is worth the total
|
|
526
|
+
number of conversions, so its Shapley values are an allocation of the
|
|
527
|
+
conversions that were actually observed. The closed form above is exactly
|
|
528
|
+
the factorial-weight Shapley definition applied to that game; it is
|
|
529
|
+
evaluated in one pass over the observed coalitions rather than over the
|
|
530
|
+
`2^n` coalition lattice.
|
|
531
|
+
|
|
532
|
+
Raw `shapley_values` and normalized `attribution` are **separate outputs
|
|
533
|
+
with separate contracts**:
|
|
441
534
|
|
|
442
535
|
| Output | Contract |
|
|
443
536
|
| --- | --- |
|
|
444
|
-
| `shapley_values` | Always present
|
|
537
|
+
| `shapley_values` | Always present, on the game's own scale (conversions), never rescaled. Under `"cumulative"` they are non-negative and sum to the observed conversions. Under `"exact"` they are signed diagnostics — not shares, percentages, or probabilities. |
|
|
445
538
|
| `attribution` | A share mapping when available, otherwise `None`. Every value is finite and in `[0, 1]`, and the values sum to 1. |
|
|
446
539
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
540
|
+
```python
|
|
541
|
+
shapley["characteristic_function"] # 'cumulative'
|
|
542
|
+
shapley["normalization_status"] # 'available'
|
|
543
|
+
shapley["raw_sum"] # 7.0 — the observed conversions
|
|
544
|
+
shapley["cancellation_ratio"] # 1.0
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
Under the legacy game, when no journey touches every channel the efficiency
|
|
548
|
+
axiom forces the raw values to sum to `v(N) = 0` by construction. Dividing
|
|
549
|
+
by that float residue produces numerically explosive output, so
|
|
550
|
+
normalization is refused instead: `attribution` is `None`, a warning is
|
|
551
|
+
emitted, and the fit still succeeds with its raw values and diagnostics
|
|
552
|
+
intact.
|
|
452
553
|
|
|
453
554
|
```python
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
555
|
+
legacy = ShapleyAttribution(
|
|
556
|
+
conversion_labels=["Purchase"], characteristic_function="exact"
|
|
557
|
+
).fit(journeys, conversions)
|
|
558
|
+
|
|
559
|
+
legacy["normalization_status"] # 'unavailable'
|
|
560
|
+
legacy["normalization_reason"] # 'near_cancellation'
|
|
561
|
+
legacy["shapley_values"] # {'Direct': -0.5833, 'Email': 0.25, 'Search': -0.0833, 'Social': 0.4167}
|
|
562
|
+
legacy["raw_sum"] # ~1e-17 — float residue, not a real total
|
|
563
|
+
legacy["raw_magnitude"] # 1.3333
|
|
564
|
+
legacy["cancellation_ratio"] # ~1e-17 — far below the 1e-12 threshold
|
|
459
565
|
```
|
|
460
566
|
|
|
461
567
|
The exact residue in `raw_sum` is not reproducible across summation orders,
|
|
@@ -471,6 +577,50 @@ codes are `zero_raw_magnitude`, `near_cancellation`, `nonfinite_result`,
|
|
|
471
577
|
Attributed conversions are only defined when `attribution` is available;
|
|
472
578
|
they are never derived from raw signed values.
|
|
473
579
|
|
|
580
|
+
#### Observation weights
|
|
581
|
+
|
|
582
|
+
`ShapleyAttribution.fit(..., weights=)` takes optional per-journey
|
|
583
|
+
observation weights — frequencies when one row stands for several identical
|
|
584
|
+
journeys, or survey and sampling weights. The coalition table becomes
|
|
585
|
+
*R_w(T) = Σ wⱼ·Yⱼ over the journeys whose channel set is exactly T*, and
|
|
586
|
+
both characteristic functions are evaluated on it as they are on *R(T)*.
|
|
587
|
+
Weighted totals are floats and are never truncated to integers. Without
|
|
588
|
+
`weights` nothing changes: the table holds integer counts.
|
|
589
|
+
|
|
590
|
+
```python
|
|
591
|
+
weights = [2.5, 1.0, 1.0, 4.0, 0.5, 0.0, 2.0, 1.5, 1.0, 3.0]
|
|
592
|
+
|
|
593
|
+
weighted_model = ShapleyAttribution(conversion_labels=["Purchase"])
|
|
594
|
+
weighted = weighted_model.fit(journeys, conversions, weights=weights)
|
|
595
|
+
|
|
596
|
+
weighted["value_function"]
|
|
597
|
+
# {('Email', 'Social'): 3.5, ('Email', 'Search'): 1.0, ('Social',): 1.0, ...}
|
|
598
|
+
print({ch: round(v, 4) for ch, v in weighted["shapley_values"].items()})
|
|
599
|
+
# {'Email': 2.9167, 'Search': 1.1667, 'Social': 3.4167}
|
|
600
|
+
print({ch: round(share, 4) for ch, share in weighted["attribution"].items()})
|
|
601
|
+
# {'Email': 0.3889, 'Search': 0.1556, 'Social': 0.4556}
|
|
602
|
+
weighted["raw_sum"] # 7.5 — the weighted conversions
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
Weights follow the `MarkovAttribution.fit(weights=)` contract: one-dimensional,
|
|
606
|
+
aligned with the journeys by position (the index labels of a pandas Series
|
|
607
|
+
are ignored), finite, nonnegative, and with a positive total. Booleans count
|
|
608
|
+
as `0` / `1`. A journey with zero weight is excluded before the channel list
|
|
609
|
+
and the coalition table are built, so a channel seen only on zero-weight
|
|
610
|
+
journeys — `Direct` above — is absent from every output. Standalone
|
|
611
|
+
`MarkovAttribution` does not drop zero-weight journeys from its channel
|
|
612
|
+
list. Integer frequency weights give the same result as repeating each
|
|
613
|
+
journey that many times.
|
|
614
|
+
|
|
615
|
+
Efficiency holds on the weighted table: under `"cumulative"` the raw values
|
|
616
|
+
sum to the weighted conversions of every nonempty coalition, and under
|
|
617
|
+
`"exact"` to `v_w(N)`. When no positively weighted journey converts,
|
|
618
|
+
`attribution` is `None` with `normalization_reason="zero_raw_magnitude"`.
|
|
619
|
+
|
|
620
|
+
[`examples/weighted_attribution.ipynb`](https://github.com/8139CAUSAL/do_attribution/blob/main/examples/weighted_attribution.ipynb)
|
|
621
|
+
works through frequency weights, fractional and zero weights, and a weighted
|
|
622
|
+
comparison on the GA4 journey extract.
|
|
623
|
+
|
|
474
624
|
### Side-by-side comparison
|
|
475
625
|
|
|
476
626
|
```python
|
|
@@ -479,21 +629,73 @@ from do_attribution import CompareAttribution
|
|
|
479
629
|
comparison = CompareAttribution(conversion_labels=["Purchase"])
|
|
480
630
|
comparison.fit(journeys, conversions)
|
|
481
631
|
|
|
482
|
-
comparison.attribution_frame() # markov
|
|
483
|
-
# Direct 0.
|
|
484
|
-
# Email 0.
|
|
485
|
-
# Search 0.
|
|
486
|
-
# Social 0.
|
|
487
|
-
comparison.unavailable_models() # {
|
|
632
|
+
comparison.attribution_frame() # markov shapley
|
|
633
|
+
# Direct 0.071429 0.142857
|
|
634
|
+
# Email 0.357143 0.309524
|
|
635
|
+
# Search 0.214286 0.166667
|
|
636
|
+
# Social 0.357143 0.380952
|
|
637
|
+
comparison.unavailable_models() # {} — every model produced a share vector
|
|
488
638
|
comparison.difference_frame("markov")
|
|
489
639
|
```
|
|
490
640
|
|
|
641
|
+
A model whose normalization is unavailable contributes an all-`NaN` column
|
|
642
|
+
and is listed by `unavailable_models()` with its reason code.
|
|
643
|
+
`shapley_characteristic_function=` selects the Shapley game.
|
|
644
|
+
|
|
491
645
|
The two methods answer different statistical questions and can disagree
|
|
492
646
|
materially without either being defective — `difference_frame` makes the
|
|
493
|
-
disagreement explicit.
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
647
|
+
disagreement explicit.
|
|
648
|
+
|
|
649
|
+
`include_baselines=True` adds the two built-in single-touch baselines as
|
|
650
|
+
fixed reference points, so that disagreement can be read against the naive
|
|
651
|
+
allocations practitioners actually use. `FirstTouchAttribution` gives each
|
|
652
|
+
converting journey's whole conversion to the first channel it touched,
|
|
653
|
+
`LastTouchAttribution` to the last. Both are `BaseAttribution` models with
|
|
654
|
+
the same `fit` / `get_attribution` / `summary` API, the same validation of
|
|
655
|
+
journeys and binary outcomes, the same `conversion_labels` handling, and the
|
|
656
|
+
same normalization contract: `attribution` is `None`, with
|
|
657
|
+
`normalization_reason="zero_raw_magnitude"`, when no conversion can be
|
|
658
|
+
credited to a channel. The raw counts are returned as
|
|
659
|
+
`credited_conversions`.
|
|
660
|
+
|
|
661
|
+
```python
|
|
662
|
+
comparison = CompareAttribution(
|
|
663
|
+
conversion_labels=["Purchase"], include_baselines=True
|
|
664
|
+
)
|
|
665
|
+
comparison.fit(journeys, conversions)
|
|
666
|
+
|
|
667
|
+
comparison.attribution_frame() # markov shapley first_touch last_touch
|
|
668
|
+
# Direct 0.071429 0.142857 0.142857 0.142857
|
|
669
|
+
# Email 0.357143 0.309524 0.142857 0.428571
|
|
670
|
+
# Search 0.214286 0.166667 0.428571 0.000000
|
|
671
|
+
# Social 0.357143 0.380952 0.285714 0.428571
|
|
672
|
+
comparison.difference_frame("last_touch")
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
The baselines are opt-in: without `include_baselines=True` the comparison
|
|
676
|
+
holds only the `markov` and `shapley` columns, exactly as before. The
|
|
677
|
+
classes can also be used on their own or passed through `extra_models`.
|
|
678
|
+
`extra_models={...}` plugs any additional `BaseAttribution` implementation
|
|
679
|
+
into the same aligned tables, so a custom baseline can sit beside the
|
|
680
|
+
built-in ones.
|
|
681
|
+
|
|
682
|
+
`CompareAttribution.fit(..., weights=)` runs a weighted comparison.
|
|
683
|
+
Zero-weight journeys are dropped first, and the remaining journeys and
|
|
684
|
+
weights are passed to both models, so Markov and Shapley are always fitted
|
|
685
|
+
on the same weighted population and neither is fitted unweighted. The
|
|
686
|
+
baselines and `extra_models` do not accept weights: a weighted fit on a
|
|
687
|
+
comparison built with `include_baselines=True` or with `extra_models` raises
|
|
688
|
+
`ValueError` before any model is fitted.
|
|
689
|
+
|
|
690
|
+
```python
|
|
691
|
+
comparison = CompareAttribution(conversion_labels=["Purchase"])
|
|
692
|
+
comparison.fit(journeys, conversions, weights=weights)
|
|
693
|
+
|
|
694
|
+
comparison.attribution_frame() # markov shapley
|
|
695
|
+
# Email 0.406250 0.388889
|
|
696
|
+
# Search 0.187500 0.155556
|
|
697
|
+
# Social 0.406250 0.455556
|
|
698
|
+
```
|
|
497
699
|
|
|
498
700
|
### Conversion-endpoint declaration
|
|
499
701
|
|
|
@@ -577,11 +779,48 @@ diagnostics) with the complete grids available programmatically via
|
|
|
577
779
|
`to_dataframes()` and the `get_*` accessors. The reporting language is
|
|
578
780
|
descriptive; it never labels a movement statistically significant or causal.
|
|
579
781
|
|
|
782
|
+
`MarkovAttribution` and `ShapleyAttribution` export their last successful
|
|
783
|
+
fit with the same `to_dataframes()` call the temporal models use. Each
|
|
784
|
+
returns a channel/share table (`attribution`), the raw-value table
|
|
785
|
+
(`removal_effects` or `shapley_values`), and a one-row `fit_diagnostics`
|
|
786
|
+
table holding the model settings and the normalization status, reason, and
|
|
787
|
+
numeric diagnostics. The Markov export also carries `conversion_probability`
|
|
788
|
+
in that row and a flat `transition_matrix` edge table (`from_state`,
|
|
789
|
+
`to_state`, `probability`), which is empty with the same columns unless the
|
|
790
|
+
model was built with `return_transition_matrix=True`. When normalization is
|
|
791
|
+
unavailable the channel rows stay in `attribution` with a missing share, so
|
|
792
|
+
an absent share is never written as zero credit, while the raw-value table
|
|
793
|
+
keeps the signed diagnostics. The tables are copies of a snapshot taken at
|
|
794
|
+
fit time: exporting never refits, and editing a returned frame changes
|
|
795
|
+
neither the model nor a later export. Calling it before `fit` raises
|
|
796
|
+
`RuntimeError`.
|
|
797
|
+
|
|
798
|
+
The Shapley `fit_diagnostics` row ends with three observation-weight
|
|
799
|
+
columns: `weights_supplied`, `total_weight` (the sum of the weights), and
|
|
800
|
+
`weighted_conversions` (the sum of weight × outcome over every supplied
|
|
801
|
+
journey). An unweighted fit records `False`, its journey count, and its
|
|
802
|
+
conversion count. In `summary()`, the journey count, conversion count, and
|
|
803
|
+
conversion rate stay plain counts of the rows passed to `fit`; a weighted
|
|
804
|
+
fit adds an `Observation weights` section with the weighted totals, and its
|
|
805
|
+
coalition tables show weighted conversions without rounding them to
|
|
806
|
+
integers. `Path diagnostics` likewise counts rows rather than weights, over
|
|
807
|
+
the rows with positive weight. With frequency weights these row counts
|
|
808
|
+
describe the rows of the frequency table, not the journeys they stand for.
|
|
809
|
+
|
|
810
|
+
```python
|
|
811
|
+
tables = model.to_dataframes() # `model` is the quick-start MarkovAttribution
|
|
812
|
+
list(tables)
|
|
813
|
+
# ['attribution', 'removal_effects', 'fit_diagnostics', 'transition_matrix']
|
|
814
|
+
|
|
815
|
+
for name, table in tables.items():
|
|
816
|
+
table.to_csv(f"markov_{name}.csv", index=False)
|
|
817
|
+
```
|
|
818
|
+
|
|
580
819
|
---
|
|
581
820
|
|
|
582
821
|
## Detailed examples
|
|
583
822
|
|
|
584
|
-
All
|
|
823
|
+
All seven notebooks run end to end on the same GA4 journey extract
|
|
585
824
|
([`examples/ga4_attribution_journeys.csv`](https://github.com/8139CAUSAL/do_attribution/blob/v0.0.2/examples/ga4_attribution_journeys.csv))
|
|
586
825
|
— a transformed journey-level
|
|
587
826
|
extract created from
|
|
@@ -594,9 +833,10 @@ path, a binary conversion flag, and an anchor timestamp.
|
|
|
594
833
|
| [`examples/basic_markov.ipynb`](https://github.com/8139CAUSAL/do_attribution/blob/v0.0.2/examples/basic_markov.ipynb) | static Markov fit, shares, removal effects, summary, transition graph |
|
|
595
834
|
| [`examples/basic_shapley.ipynb`](https://github.com/8139CAUSAL/do_attribution/blob/v0.0.2/examples/basic_shapley.ipynb) | Shapley shares, raw values, coalition results, coalition plot |
|
|
596
835
|
| [`examples/side_by_side.ipynb`](https://github.com/8139CAUSAL/do_attribution/blob/v0.0.2/examples/side_by_side.ipynb) | `CompareAttribution` aligned tables, differences, comparison plot |
|
|
836
|
+
| [`examples/weighted_attribution.ipynb`](https://github.com/8139CAUSAL/do_attribution/blob/main/examples/weighted_attribution.ipynb) | optional `weights=`: frequency weights that reproduce the one-row-per-journey Shapley fit, fractional and zero weights, the weighted summary and export, and a weighted `CompareAttribution` on a down-sampled journey table |
|
|
597
837
|
| [`examples/temporal_markov.ipynb`](https://github.com/8139CAUSAL/do_attribution/blob/v0.0.2/examples/temporal_markov.ipynb) | weekly Markov epochs, diagnostics, `to_dataframes()`, epoch recommendation |
|
|
598
838
|
| [`examples/temporal_shapley.ipynb`](https://github.com/8139CAUSAL/do_attribution/blob/v0.0.2/examples/temporal_shapley.ipynb) | weekly Shapley epochs, channel trends, diagnostics, epoch recommendation |
|
|
599
|
-
| [`examples/attribution_experiment_calibration.ipynb`](https://github.com/8139CAUSAL/do_attribution/blob/v0.0.2/examples/attribution_experiment_calibration.ipynb) | seeded semi-synthetic randomized campaign on the GA4 rows, matched static and temporal Markov estimates, calibration, and seven diagnostics |
|
|
839
|
+
| [`examples/attribution_experiment_calibration.ipynb`](https://github.com/8139CAUSAL/do_attribution/blob/v0.0.2/examples/attribution_experiment_calibration.ipynb) | seeded semi-synthetic randomized campaign on the GA4 rows, matched static and temporal Markov estimates, calibration, the experimental-evidence figure, and seven calibration diagnostics |
|
|
600
840
|
|
|
601
841
|
|
|
602
842
|
---
|