analytics-toolkit 1.3.7.3__tar.gz → 1.3.8.2__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.
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/PKG-INFO +106 -8
- analytics_toolkit-1.3.8.2/README.md +153 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/ab_utils/api.py +42 -2
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/ab_utils/cuped.py +70 -11
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/ab_utils/metrics.py +4 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/ab_utils/parallel.py +41 -2
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/ab_utils/rows.py +200 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/cli.py +34 -1
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/__init__.py +19 -58
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/_backend_adapters/base.py +5 -1
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/_backend_adapters/clickhouse.py +3 -1
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/clickhouse/__init__.py +2 -2
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/clickhouse/lifecycle.py +33 -15
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/clickhouse/options.py +1 -24
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/connection/__init__.py +1 -6
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/connection/get_sql_connection.py +3 -26
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/ddl/__init__.py +0 -4
- analytics_toolkit-1.3.8.2/analytics_toolkit/sql/ddl/api.py +635 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/ddl/models.py +3 -6
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/dml/__init__.py +6 -10
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/dml/io/__init__.py +2 -2
- analytics_toolkit-1.3.8.2/analytics_toolkit/sql/dml/io/cancel_queries.py +211 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/dml/io/execute_read.py +4 -4
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/dml/io/execute_sql.py +4 -4
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/dml/io/read_sql.py +8 -8
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/dml/load/load_df.py +20 -33
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/dml/load/models.py +0 -1
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/dml/load/stage.py +7 -6
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/dml/table/__init__.py +6 -19
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/dml/table/_basic_ops.py +5 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/dml/table/api.py +3 -4
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/dml/table/ch_create_table_as.py +73 -69
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/dml/table/create_table_from_sql.py +33 -20
- analytics_toolkit-1.3.8.2/analytics_toolkit/sql/dml/table/drop_tables.py +411 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/dml/table/maintenance.py +13 -5
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/dml/table/models.py +4 -23
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/dml/table/partitions.py +34 -21
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/dml/table/write_modes.py +11 -11
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/dml/transfer/flow/api.py +3 -15
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/dml/transfer/flow/finalize.py +0 -3
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/dml/transfer/runtime/models.py +0 -1
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/orchestration/tasks.py +21 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit.egg-info/PKG-INFO +106 -8
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit.egg-info/SOURCES.txt +5 -6
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/pyproject.toml +1 -1
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/tests/test_ab_utils_metrics.py +161 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/tests/test_ab_utils_parallel.py +172 -7
- analytics_toolkit-1.3.8.2/tests/test_rag_docs.py +264 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/tests/test_sql_architecture.py +89 -12
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/tests/test_sql_async.py +204 -69
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/tests/test_sql_backend_adapters.py +26 -14
- analytics_toolkit-1.3.8.2/tests/test_sql_cancel_queries.py +363 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/tests/test_sql_ch_create_table_as.py +48 -1
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/tests/test_sql_connection_config.py +53 -60
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/tests/test_sql_create_table_from_sql.py +117 -2
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/tests/test_sql_drop_many_partitions.py +39 -75
- analytics_toolkit-1.3.7.3/tests/test_sql_ch_drop_table.py → analytics_toolkit-1.3.8.2/tests/test_sql_drop_tables.py +96 -44
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/tests/test_sql_gp_create_partitions.py +101 -85
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/tests/test_sql_improvements.py +115 -67
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/tests/test_sql_load_table.py +26 -24
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/tests/test_sql_parallel.py +179 -48
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/tests/test_sql_retries.py +9 -2
- analytics_toolkit-1.3.7.3/README.md +0 -55
- analytics_toolkit-1.3.7.3/analytics_toolkit/sql/ddl/api.py +0 -246
- analytics_toolkit-1.3.7.3/analytics_toolkit/sql/dml/io/gp_cancel.py +0 -83
- analytics_toolkit-1.3.7.3/analytics_toolkit/sql/dml/table/ch_drop_table.py +0 -214
- analytics_toolkit-1.3.7.3/analytics_toolkit/sql/dml/table/ch_full_table_move.py +0 -1096
- analytics_toolkit-1.3.7.3/tests/test_sql_ch_full_table_move.py +0 -463
- analytics_toolkit-1.3.7.3/tests/test_sql_gp_cancel.py +0 -223
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/LICENSE +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/__init__.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/ab_utils/__init__.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/ab_utils/bootstrap.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/ab_utils/constants.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/ab_utils/formatter.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/ab_utils/outliers.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/ab_utils/planning.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/ab_utils/ratio.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/ab_utils/split.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/ab_utils/stats.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/ab_utils/validation.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/dates/__init__.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/dates/dates.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/dates/functions.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/excel/__init__.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/excel/long_format.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/general/__init__.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/general/logging.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/general/read_file.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/_backend_adapters/__init__.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/_backend_adapters/dbapi.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/_backend_adapters/gp.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/_backend_adapters/registry.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/_backend_adapters/trino.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/_backend_adapters/utils.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/backend_adapters.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/clickhouse/wait.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/connection/config.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/connection/errors.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/connection/protocols.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/core/__init__.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/core/capabilities.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/core/identifiers.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/core/types.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/ddl/builders.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/ddl/clickhouse.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/ddl/extract_ddl.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/ddl/identifiers.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/ddl/schema.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/dml/io/models.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/dml/load/__init__.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/dml/load/load_sql_table.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/dml/table/table_validation.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/dml/transfer/__init__.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/dml/transfer/flow/__init__.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/dml/transfer/flow/attempt.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/dml/transfer/flow/estimate.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/dml/transfer/flow/stage.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/dml/transfer/io/__init__.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/dml/transfer/io/source.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/dml/transfer/runtime/__init__.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/dml/transfer/runtime/retry.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/dml/transfer/schema.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/execution/__init__.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/execution/labels.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/execution/operation_runner.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/execution/plan_steps.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/execution/plans.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/execution/query_timing.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/metadata/__init__.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/metadata/show_tables.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/metadata/table_info.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/orchestration/__init__.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/orchestration/async_sql.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/orchestration/parallel_sql.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/sql/orchestration/pipeline.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit.egg-info/dependency_links.txt +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit.egg-info/entry_points.txt +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit.egg-info/requires.txt +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit.egg-info/top_level.txt +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/setup.cfg +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/tests/test_ab_utils_format.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/tests/test_ab_utils_split.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/tests/test_dates.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/tests/test_excel_long_format.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/tests/test_general_logging.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/tests/test_general_read_file.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/tests/test_sql_execute_read.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/tests/test_sql_extract_ddl.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/tests/test_sql_show_tables.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/tests/test_sql_transfer_clickhouse.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/tests/test_sql_transfer_row_batches.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/tests/test_sql_transfer_schema.py +0 -0
- {analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/tests/test_sql_transfer_source_batches.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: analytics-toolkit
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.8.2
|
|
4
4
|
Summary: Shared Python utilities for SQL, Excel, and date helpers.
|
|
5
5
|
Author: analytics_toolkit contributors
|
|
6
6
|
License: MIT License
|
|
@@ -75,7 +75,7 @@ Python toolkit for AB-test analysis, SQL workflows, Excel reports, and date help
|
|
|
75
75
|
|
|
76
76
|
[Documentation Overview](https://github.com/Karapsin/analytics_toolkit/blob/main/docs/README.md)
|
|
77
77
|
|
|
78
|
-
**Version:** `1.3.
|
|
78
|
+
**Version:** `1.3.8.2`<br>
|
|
79
79
|
**Depends:** Python (`>=3.8,<3.15`)<br>
|
|
80
80
|
**Imports:** [clickhouse-connect](https://pypi.org/project/clickhouse-connect/) (`>=0.5.14,<1`), [lz4](https://pypi.org/project/lz4/) (`>=4.3.2,<5`), [numpy](https://pypi.org/project/numpy/) (`>=1.24.2,<2`), [openpyxl](https://pypi.org/project/openpyxl/) (`>=3.1.1,<4`), [orjson](https://pypi.org/project/orjson/) (`>=3.8.7,<4`), [pandas](https://pypi.org/project/pandas/) (`>=1.4.4,<3`), [psycopg2-binary](https://pypi.org/project/psycopg2-binary/) (`>=2.9.5,<3`), [python-dateutil](https://pypi.org/project/python-dateutil/) (`>=2.8.2,<3`), [pytz](https://pypi.org/project/pytz/) (`>=2022.7`), [requests](https://pypi.org/project/requests/) (`>=2.28.2,<3`), [scipy](https://pypi.org/project/scipy/) (`>=1.10.1,<2`), [sqlglot](https://pypi.org/project/sqlglot/) (`>=20,<31`), [sqlparse](https://pypi.org/project/sqlparse/) (`>=0.4.3,<1`), [tqdm](https://pypi.org/project/tqdm/) (`>=4.65.0,<5`), [trino](https://pypi.org/project/trino/) (`>=0.320,<1`), [zstandard](https://pypi.org/project/zstandard/) (`>=0.20.0,<1`)<br>
|
|
81
81
|
**Suggests:** [apache-airflow](https://pypi.org/project/apache-airflow/) (`>=2.4,<3`; optional extra `airflow`)<br>
|
|
@@ -107,13 +107,111 @@ pip install git+https://github.com/Karapsin/analytics_toolkit.git
|
|
|
107
107
|
- `analytics_toolkit.dates`: date and period helpers.
|
|
108
108
|
- `analytics_toolkit.general`: shared logging and file path helpers.
|
|
109
109
|
|
|
110
|
-
## SQL
|
|
110
|
+
## SQL Workflows
|
|
111
111
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
112
|
+
[All SQL functions](https://github.com/Karapsin/analytics_toolkit/blob/main/docs/modules/sql/functions/index.md)
|
|
113
|
+
|
|
114
|
+
[SQL module guide](https://github.com/Karapsin/analytics_toolkit/blob/main/docs/modules/sql/index.md)
|
|
115
|
+
|
|
116
|
+
`sql.transfer` streams query results between configured SQL backends, with
|
|
117
|
+
batching, retries, and table creation or replacement handled by one call.
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
from analytics_toolkit import sql
|
|
121
|
+
|
|
122
|
+
rows = sql.transfer(
|
|
123
|
+
from_db="trino",
|
|
124
|
+
to_db="gp",
|
|
125
|
+
from_sql="select user_id, order_id, amount from iceberg.analytics.orders",
|
|
126
|
+
to_table="sandbox.orders_copy",
|
|
127
|
+
write_mode="replace",
|
|
128
|
+
batch_size=50_000,
|
|
129
|
+
progress=True,
|
|
130
|
+
)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Aliases can point to the same backend type, so Greenplum-to-Greenplum transfers
|
|
134
|
+
work the same way.
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
rows = sql.transfer(
|
|
138
|
+
from_db="gp_sales",
|
|
139
|
+
to_db="gp_finance",
|
|
140
|
+
from_sql="select user_id, order_id, amount from mart.sales_orders",
|
|
141
|
+
to_table="finance.sales_orders_copy",
|
|
142
|
+
write_mode="replace",
|
|
143
|
+
batch_size=50_000,
|
|
144
|
+
)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
- `sql.read`: run a query and return a dataframe.
|
|
148
|
+
- `sql.execute`: run DDL or DML without returning a dataframe.
|
|
149
|
+
- `sql.execute_read`: run setup SQL and return the final result as a dataframe.
|
|
150
|
+
- `sql.load_df`: load a pandas dataframe into a configured backend table.
|
|
151
|
+
- `sql.transfer`: move rows from a source query to a target table across backends.
|
|
152
|
+
|
|
153
|
+
## AB Metrics
|
|
154
|
+
|
|
155
|
+
[All AB functions](https://github.com/Karapsin/analytics_toolkit/blob/main/docs/modules/ab_utils/functions/index.md)
|
|
156
|
+
|
|
157
|
+
[AB utilities guide](https://github.com/Karapsin/analytics_toolkit/blob/main/docs/modules/ab_utils/index.md)
|
|
158
|
+
|
|
159
|
+
`compute_test_metrics` compares experiment groups across mean and ratio metrics,
|
|
160
|
+
with optional CUPED statistics and bootstrap multiple-comparison adjustment.
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
from analytics_toolkit.ab_utils import compute_test_metrics
|
|
164
|
+
|
|
165
|
+
result = compute_test_metrics(
|
|
166
|
+
experiment_df,
|
|
167
|
+
group="group_name",
|
|
168
|
+
control="control",
|
|
169
|
+
user_id="user_id",
|
|
170
|
+
ratio_metrics=[
|
|
171
|
+
{"name": "ctr", "numerator": "clicks", "denominator": "views"},
|
|
172
|
+
],
|
|
173
|
+
pre_exp_metrics_df=pre_experiment_df,
|
|
174
|
+
multiple_comparisons_adjustment=True,
|
|
175
|
+
multiple_comparisons_adjustment_resamples=1000,
|
|
176
|
+
)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Example output with CUPED and bootstrap columns enabled:
|
|
180
|
+
|
|
181
|
+
| metric_type | group_1 | group_2 | metric_name | n0 | n1 | outliers_cutoff | outliers_n_control | outliers_n_test | metric_control | metric_test | variance_control | variance_test | delta_abs | delta_relative | mde_abs | mde_relative | s.e. | p-value | s.e. CUPED | p-value CUPED | mde_abs CUPED | mde_relative CUPED | s.e. bootstrap | bootstrap_adj_p |
|
|
182
|
+
| --- | --- | --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
|
183
|
+
| mean | test | control | revenue | 10000 | 10050 | 250.0 | 3 | 4 | 12.40 | 13.10 | 45.20 | 47.80 | 0.70 | 0.056 | 0.42 | 0.034 | 0.15 | 0.003 | 0.11 | 0.001 | 0.31 | 0.025 | 0.14 | 0.012 |
|
|
184
|
+
| ratio | test | control | ctr | 10000 | 10050 | 1.0 | 0 | 0 | 0.082 | 0.087 | 0.0009 | 0.0010 | 0.005 | 0.061 | 0.003 | 0.037 | 0.001 | 0.008 | 0.001 | 0.006 | 0.002 | 0.024 | 0.001 | 0.019 |
|
|
185
|
+
|
|
186
|
+
## Date Helpers
|
|
187
|
+
|
|
188
|
+
[All date functions](https://github.com/Karapsin/analytics_toolkit/blob/main/docs/modules/dates/functions/index.md)
|
|
189
|
+
|
|
190
|
+
[Date helpers guide](https://github.com/Karapsin/analytics_toolkit/blob/main/docs/modules/dates/index.md)
|
|
191
|
+
|
|
192
|
+
Date helpers cover reporting ranges, period boundaries, offsets, and stable
|
|
193
|
+
string formatting for SQL and filenames.
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
from analytics_toolkit.dates import add_days, first_day, gen_dates_list, last_day
|
|
197
|
+
|
|
198
|
+
report_days = gen_dates_list("2026-06-01", "2026-06-07")
|
|
199
|
+
# ["2026-06-01", "2026-06-02", "2026-06-03", "2026-06-04", "2026-06-05", "2026-06-06", "2026-06-07"]
|
|
200
|
+
|
|
201
|
+
month_start = first_day("2026-06-08", "month")
|
|
202
|
+
# "2026-06-01"
|
|
203
|
+
|
|
204
|
+
month_end = last_day("2026-06-08", "month")
|
|
205
|
+
# "2026-06-30"
|
|
206
|
+
|
|
207
|
+
next_run = add_days("2026-06-08", 1)
|
|
208
|
+
# "2026-06-09"
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
- `gen_dates_list`: build daily, weekly, monthly, or quarterly sequences.
|
|
212
|
+
- `first_day` / `last_day`: get week, month, or quarter boundaries.
|
|
213
|
+
- `add_days`, `add_weeks`, `add_months`, `add_quarters`: shift dates.
|
|
214
|
+
- `sanitize_date`: convert a date to compact `YYYYMMDD` text.
|
|
117
215
|
|
|
118
216
|
## Documentation
|
|
119
217
|
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# analytics_toolkit
|
|
2
|
+
|
|
3
|
+
Python toolkit for AB-test analysis, SQL workflows, Excel reports, and date helpers.
|
|
4
|
+
|
|
5
|
+
[Quick Start Guide](https://github.com/Karapsin/analytics_toolkit/blob/main/docs/QUICK_START.md)
|
|
6
|
+
|
|
7
|
+
[Documentation Overview](https://github.com/Karapsin/analytics_toolkit/blob/main/docs/README.md)
|
|
8
|
+
|
|
9
|
+
**Version:** `1.3.8.2`<br>
|
|
10
|
+
**Depends:** Python (`>=3.8,<3.15`)<br>
|
|
11
|
+
**Imports:** [clickhouse-connect](https://pypi.org/project/clickhouse-connect/) (`>=0.5.14,<1`), [lz4](https://pypi.org/project/lz4/) (`>=4.3.2,<5`), [numpy](https://pypi.org/project/numpy/) (`>=1.24.2,<2`), [openpyxl](https://pypi.org/project/openpyxl/) (`>=3.1.1,<4`), [orjson](https://pypi.org/project/orjson/) (`>=3.8.7,<4`), [pandas](https://pypi.org/project/pandas/) (`>=1.4.4,<3`), [psycopg2-binary](https://pypi.org/project/psycopg2-binary/) (`>=2.9.5,<3`), [python-dateutil](https://pypi.org/project/python-dateutil/) (`>=2.8.2,<3`), [pytz](https://pypi.org/project/pytz/) (`>=2022.7`), [requests](https://pypi.org/project/requests/) (`>=2.28.2,<3`), [scipy](https://pypi.org/project/scipy/) (`>=1.10.1,<2`), [sqlglot](https://pypi.org/project/sqlglot/) (`>=20,<31`), [sqlparse](https://pypi.org/project/sqlparse/) (`>=0.4.3,<1`), [tqdm](https://pypi.org/project/tqdm/) (`>=4.65.0,<5`), [trino](https://pypi.org/project/trino/) (`>=0.320,<1`), [zstandard](https://pypi.org/project/zstandard/) (`>=0.20.0,<1`)<br>
|
|
12
|
+
**Suggests:** [apache-airflow](https://pypi.org/project/apache-airflow/) (`>=2.4,<3`; optional extra `airflow`)<br>
|
|
13
|
+
**Install:** `pip install analytics-toolkit`<br>
|
|
14
|
+
**PyPI:** [pypi.org/project/analytics-toolkit](https://pypi.org/project/analytics-toolkit/)<br>
|
|
15
|
+
**License:** MIT<br>
|
|
16
|
+
**Source:** [github.com/Karapsin/analytics_toolkit](https://github.com/Karapsin/analytics_toolkit)<br>
|
|
17
|
+
**Issues:** [GitHub Issues](https://github.com/Karapsin/analytics_toolkit/issues)
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
From PyPI:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install analytics-toolkit
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
From GitHub:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install git+https://github.com/Karapsin/analytics_toolkit.git
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Areas
|
|
34
|
+
|
|
35
|
+
- `analytics_toolkit.ab_utils`: AB-test metric comparison helpers.
|
|
36
|
+
- `analytics_toolkit.sql`: SQL read, execute, load, and transfer helpers.
|
|
37
|
+
- `analytics_toolkit.excel`: Excel report helpers for long-format dataframes.
|
|
38
|
+
- `analytics_toolkit.dates`: date and period helpers.
|
|
39
|
+
- `analytics_toolkit.general`: shared logging and file path helpers.
|
|
40
|
+
|
|
41
|
+
## SQL Workflows
|
|
42
|
+
|
|
43
|
+
[All SQL functions](https://github.com/Karapsin/analytics_toolkit/blob/main/docs/modules/sql/functions/index.md)
|
|
44
|
+
|
|
45
|
+
[SQL module guide](https://github.com/Karapsin/analytics_toolkit/blob/main/docs/modules/sql/index.md)
|
|
46
|
+
|
|
47
|
+
`sql.transfer` streams query results between configured SQL backends, with
|
|
48
|
+
batching, retries, and table creation or replacement handled by one call.
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from analytics_toolkit import sql
|
|
52
|
+
|
|
53
|
+
rows = sql.transfer(
|
|
54
|
+
from_db="trino",
|
|
55
|
+
to_db="gp",
|
|
56
|
+
from_sql="select user_id, order_id, amount from iceberg.analytics.orders",
|
|
57
|
+
to_table="sandbox.orders_copy",
|
|
58
|
+
write_mode="replace",
|
|
59
|
+
batch_size=50_000,
|
|
60
|
+
progress=True,
|
|
61
|
+
)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Aliases can point to the same backend type, so Greenplum-to-Greenplum transfers
|
|
65
|
+
work the same way.
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
rows = sql.transfer(
|
|
69
|
+
from_db="gp_sales",
|
|
70
|
+
to_db="gp_finance",
|
|
71
|
+
from_sql="select user_id, order_id, amount from mart.sales_orders",
|
|
72
|
+
to_table="finance.sales_orders_copy",
|
|
73
|
+
write_mode="replace",
|
|
74
|
+
batch_size=50_000,
|
|
75
|
+
)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
- `sql.read`: run a query and return a dataframe.
|
|
79
|
+
- `sql.execute`: run DDL or DML without returning a dataframe.
|
|
80
|
+
- `sql.execute_read`: run setup SQL and return the final result as a dataframe.
|
|
81
|
+
- `sql.load_df`: load a pandas dataframe into a configured backend table.
|
|
82
|
+
- `sql.transfer`: move rows from a source query to a target table across backends.
|
|
83
|
+
|
|
84
|
+
## AB Metrics
|
|
85
|
+
|
|
86
|
+
[All AB functions](https://github.com/Karapsin/analytics_toolkit/blob/main/docs/modules/ab_utils/functions/index.md)
|
|
87
|
+
|
|
88
|
+
[AB utilities guide](https://github.com/Karapsin/analytics_toolkit/blob/main/docs/modules/ab_utils/index.md)
|
|
89
|
+
|
|
90
|
+
`compute_test_metrics` compares experiment groups across mean and ratio metrics,
|
|
91
|
+
with optional CUPED statistics and bootstrap multiple-comparison adjustment.
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from analytics_toolkit.ab_utils import compute_test_metrics
|
|
95
|
+
|
|
96
|
+
result = compute_test_metrics(
|
|
97
|
+
experiment_df,
|
|
98
|
+
group="group_name",
|
|
99
|
+
control="control",
|
|
100
|
+
user_id="user_id",
|
|
101
|
+
ratio_metrics=[
|
|
102
|
+
{"name": "ctr", "numerator": "clicks", "denominator": "views"},
|
|
103
|
+
],
|
|
104
|
+
pre_exp_metrics_df=pre_experiment_df,
|
|
105
|
+
multiple_comparisons_adjustment=True,
|
|
106
|
+
multiple_comparisons_adjustment_resamples=1000,
|
|
107
|
+
)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Example output with CUPED and bootstrap columns enabled:
|
|
111
|
+
|
|
112
|
+
| metric_type | group_1 | group_2 | metric_name | n0 | n1 | outliers_cutoff | outliers_n_control | outliers_n_test | metric_control | metric_test | variance_control | variance_test | delta_abs | delta_relative | mde_abs | mde_relative | s.e. | p-value | s.e. CUPED | p-value CUPED | mde_abs CUPED | mde_relative CUPED | s.e. bootstrap | bootstrap_adj_p |
|
|
113
|
+
| --- | --- | --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
|
114
|
+
| mean | test | control | revenue | 10000 | 10050 | 250.0 | 3 | 4 | 12.40 | 13.10 | 45.20 | 47.80 | 0.70 | 0.056 | 0.42 | 0.034 | 0.15 | 0.003 | 0.11 | 0.001 | 0.31 | 0.025 | 0.14 | 0.012 |
|
|
115
|
+
| ratio | test | control | ctr | 10000 | 10050 | 1.0 | 0 | 0 | 0.082 | 0.087 | 0.0009 | 0.0010 | 0.005 | 0.061 | 0.003 | 0.037 | 0.001 | 0.008 | 0.001 | 0.006 | 0.002 | 0.024 | 0.001 | 0.019 |
|
|
116
|
+
|
|
117
|
+
## Date Helpers
|
|
118
|
+
|
|
119
|
+
[All date functions](https://github.com/Karapsin/analytics_toolkit/blob/main/docs/modules/dates/functions/index.md)
|
|
120
|
+
|
|
121
|
+
[Date helpers guide](https://github.com/Karapsin/analytics_toolkit/blob/main/docs/modules/dates/index.md)
|
|
122
|
+
|
|
123
|
+
Date helpers cover reporting ranges, period boundaries, offsets, and stable
|
|
124
|
+
string formatting for SQL and filenames.
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from analytics_toolkit.dates import add_days, first_day, gen_dates_list, last_day
|
|
128
|
+
|
|
129
|
+
report_days = gen_dates_list("2026-06-01", "2026-06-07")
|
|
130
|
+
# ["2026-06-01", "2026-06-02", "2026-06-03", "2026-06-04", "2026-06-05", "2026-06-06", "2026-06-07"]
|
|
131
|
+
|
|
132
|
+
month_start = first_day("2026-06-08", "month")
|
|
133
|
+
# "2026-06-01"
|
|
134
|
+
|
|
135
|
+
month_end = last_day("2026-06-08", "month")
|
|
136
|
+
# "2026-06-30"
|
|
137
|
+
|
|
138
|
+
next_run = add_days("2026-06-08", 1)
|
|
139
|
+
# "2026-06-09"
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
- `gen_dates_list`: build daily, weekly, monthly, or quarterly sequences.
|
|
143
|
+
- `first_day` / `last_day`: get week, month, or quarter boundaries.
|
|
144
|
+
- `add_days`, `add_weeks`, `add_months`, `add_quarters`: shift dates.
|
|
145
|
+
- `sanitize_date`: convert a date to compact `YYYYMMDD` text.
|
|
146
|
+
|
|
147
|
+
## Documentation
|
|
148
|
+
|
|
149
|
+
- [Documentation Overview](https://github.com/Karapsin/analytics_toolkit/blob/main/docs/README.md)
|
|
150
|
+
- [Quick Start](https://github.com/Karapsin/analytics_toolkit/blob/main/docs/QUICK_START.md)
|
|
151
|
+
- [Airflow SQL Manual](https://github.com/Karapsin/analytics_toolkit/blob/main/docs/AIRFLOW_SQL_MANUAL.md)
|
|
152
|
+
- [Module documentation](https://github.com/Karapsin/analytics_toolkit/blob/main/docs/modules/README.md)
|
|
153
|
+
- [Changelog](https://github.com/Karapsin/analytics_toolkit/blob/main/docs/CHANGELOG.md)
|
|
@@ -6,10 +6,15 @@ from analytics_toolkit.general import time_print
|
|
|
6
6
|
|
|
7
7
|
from .bootstrap import _apply_multiple_comparisons_adjustment
|
|
8
8
|
from .constants import DEFAULT_ALPHA, DEFAULT_POWER
|
|
9
|
-
from .cuped import _compute_cuped_statistics
|
|
9
|
+
from .cuped import _compute_cuped_statistics, _prepare_cuped_context
|
|
10
10
|
from .outliers import _build_outlier_context
|
|
11
11
|
from .ratio import _normalize_ratio_metrics
|
|
12
|
-
from .rows import
|
|
12
|
+
from .rows import (
|
|
13
|
+
_build_comparisons,
|
|
14
|
+
_build_metric_definitions,
|
|
15
|
+
_build_metric_row,
|
|
16
|
+
_prepare_metric_context,
|
|
17
|
+
)
|
|
13
18
|
from .stats import _compute_mde_from_standard_error, _safe_relative
|
|
14
19
|
from .validation import (
|
|
15
20
|
_validate_input_columns,
|
|
@@ -105,6 +110,15 @@ def compute_test_metrics(
|
|
|
105
110
|
ratio_specs = _normalize_ratio_metrics(df, ratio_metrics, reserved_columns={group, user_id})
|
|
106
111
|
comparisons = _build_comparisons(group_names, control, test_vs_test=test_vs_test)
|
|
107
112
|
metric_definitions = _build_metric_definitions(metric_columns, ratio_specs)
|
|
113
|
+
group_values = df[group].to_numpy()
|
|
114
|
+
group_masks = {group_name: group_values == group_name for group_name in group_names}
|
|
115
|
+
comparison_frames = {
|
|
116
|
+
(test_group, baseline_group): df.loc[
|
|
117
|
+
group_masks[test_group] | group_masks[baseline_group],
|
|
118
|
+
[user_id, group],
|
|
119
|
+
].copy()
|
|
120
|
+
for test_group, baseline_group in comparisons
|
|
121
|
+
}
|
|
108
122
|
time_print(
|
|
109
123
|
"compute_test_metrics: setup complete "
|
|
110
124
|
f"groups={len(group_names)} comparisons={len(comparisons)} "
|
|
@@ -129,6 +143,26 @@ def compute_test_metrics(
|
|
|
129
143
|
)
|
|
130
144
|
time_print("compute_test_metrics: outlier contexts complete")
|
|
131
145
|
|
|
146
|
+
prepared_metric_contexts: dict[str, dict[str, object]] = {}
|
|
147
|
+
prepared_cuped_contexts: dict[str, dict[str, object]] = {}
|
|
148
|
+
for metric_definition in metric_definitions:
|
|
149
|
+
metric_key = str(metric_definition["metric_key"])
|
|
150
|
+
outlier_context = metric_definition.get("_outlier_context")
|
|
151
|
+
prepared_metric_contexts[metric_key] = _prepare_metric_context(
|
|
152
|
+
df=df,
|
|
153
|
+
metric_definition=metric_definition,
|
|
154
|
+
outlier_context=outlier_context,
|
|
155
|
+
)
|
|
156
|
+
if pre_exp_metrics_df is not None:
|
|
157
|
+
prepared_cuped_contexts[metric_key] = _prepare_cuped_context(
|
|
158
|
+
df=df,
|
|
159
|
+
pre_exp_metrics_df=pre_exp_metrics_df,
|
|
160
|
+
user_id_column=user_id,
|
|
161
|
+
metric_definition=metric_definition,
|
|
162
|
+
outlier_context=outlier_context,
|
|
163
|
+
pre_outlier_context=metric_definition.get("_pre_outlier_context"),
|
|
164
|
+
)
|
|
165
|
+
|
|
132
166
|
rows: list[dict[str, object]] = []
|
|
133
167
|
for test_group, baseline_group in comparisons:
|
|
134
168
|
time_print(f"compute_test_metrics: comparison {test_group} vs {baseline_group}")
|
|
@@ -146,6 +180,8 @@ def compute_test_metrics(
|
|
|
146
180
|
mde_alpha=mde_alpha,
|
|
147
181
|
mde_power=mde_power,
|
|
148
182
|
outlier_context=outlier_context,
|
|
183
|
+
prepared_metric_context=prepared_metric_contexts.get(metric_name),
|
|
184
|
+
group_masks=group_masks,
|
|
149
185
|
)
|
|
150
186
|
row["_comparison_key"] = (test_group, baseline_group)
|
|
151
187
|
if pre_exp_metrics_df is not None:
|
|
@@ -160,6 +196,10 @@ def compute_test_metrics(
|
|
|
160
196
|
metric_definition=metric_definition,
|
|
161
197
|
outlier_context=outlier_context,
|
|
162
198
|
pre_outlier_context=metric_definition.get("_pre_outlier_context"),
|
|
199
|
+
prepared_cuped_context={
|
|
200
|
+
**prepared_cuped_contexts[metric_name],
|
|
201
|
+
"comparison_frame": comparison_frames[(test_group, baseline_group)],
|
|
202
|
+
},
|
|
163
203
|
)
|
|
164
204
|
row["mde_abs CUPED"] = _compute_mde_from_standard_error(
|
|
165
205
|
standard_error=row["s.e. CUPED"],
|
|
@@ -41,29 +41,68 @@ def _compute_cuped_p_value(
|
|
|
41
41
|
return p_value
|
|
42
42
|
|
|
43
43
|
|
|
44
|
-
def
|
|
44
|
+
def _prepare_cuped_context(
|
|
45
45
|
df: pd.DataFrame,
|
|
46
46
|
pre_exp_metrics_df: pd.DataFrame,
|
|
47
|
-
group_column: str,
|
|
48
47
|
user_id_column: str,
|
|
49
|
-
baseline_group: str,
|
|
50
|
-
test_group: str,
|
|
51
48
|
metric_definition: dict[str, object],
|
|
52
49
|
outlier_context: dict[str, object] | None = None,
|
|
53
50
|
pre_outlier_context: dict[str, object] | None = None,
|
|
54
|
-
) ->
|
|
55
|
-
|
|
51
|
+
) -> dict[str, object]:
|
|
52
|
+
exp_values, exp_error = _build_metric_values_by_user(
|
|
56
53
|
df=df,
|
|
57
|
-
pre_exp_metrics_df=pre_exp_metrics_df,
|
|
58
54
|
user_id_column=user_id_column,
|
|
59
|
-
group_column=group_column,
|
|
60
|
-
baseline_group=baseline_group,
|
|
61
|
-
test_group=test_group,
|
|
62
55
|
metric_definition=metric_definition,
|
|
56
|
+
value_column="metric_exp",
|
|
63
57
|
outlier_context=outlier_context,
|
|
64
|
-
pre_outlier_context=pre_outlier_context,
|
|
65
58
|
)
|
|
59
|
+
pre_values, pre_error = _build_metric_values_by_user(
|
|
60
|
+
df=pre_exp_metrics_df,
|
|
61
|
+
user_id_column=user_id_column,
|
|
62
|
+
metric_definition=metric_definition,
|
|
63
|
+
value_column="metric_pre",
|
|
64
|
+
outlier_context=pre_outlier_context,
|
|
65
|
+
)
|
|
66
|
+
return {
|
|
67
|
+
"exp_values": exp_values.set_index(user_id_column) if exp_error is None else None,
|
|
68
|
+
"pre_values": pre_values.set_index(user_id_column) if pre_error is None else None,
|
|
69
|
+
"exp_error": exp_error,
|
|
70
|
+
"pre_error": pre_error,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _compute_cuped_statistics(
|
|
75
|
+
df: pd.DataFrame,
|
|
76
|
+
pre_exp_metrics_df: pd.DataFrame,
|
|
77
|
+
group_column: str,
|
|
78
|
+
user_id_column: str,
|
|
79
|
+
baseline_group: str,
|
|
80
|
+
test_group: str,
|
|
81
|
+
metric_definition: dict[str, object],
|
|
82
|
+
outlier_context: dict[str, object] | None = None,
|
|
83
|
+
pre_outlier_context: dict[str, object] | None = None,
|
|
84
|
+
prepared_cuped_context: dict[str, object] | None = None,
|
|
85
|
+
) -> tuple[float, float]:
|
|
66
86
|
metric_name = str(metric_definition["metric_key"])
|
|
87
|
+
if prepared_cuped_context is None:
|
|
88
|
+
cuped_frame, reason = _build_cuped_frame(
|
|
89
|
+
df=df,
|
|
90
|
+
pre_exp_metrics_df=pre_exp_metrics_df,
|
|
91
|
+
user_id_column=user_id_column,
|
|
92
|
+
group_column=group_column,
|
|
93
|
+
baseline_group=baseline_group,
|
|
94
|
+
test_group=test_group,
|
|
95
|
+
metric_definition=metric_definition,
|
|
96
|
+
outlier_context=outlier_context,
|
|
97
|
+
pre_outlier_context=pre_outlier_context,
|
|
98
|
+
)
|
|
99
|
+
else:
|
|
100
|
+
cuped_frame, reason = _build_cuped_frame_from_prepared_context(
|
|
101
|
+
comparison_frame=prepared_cuped_context["comparison_frame"],
|
|
102
|
+
user_id_column=user_id_column,
|
|
103
|
+
prepared_cuped_context=prepared_cuped_context,
|
|
104
|
+
)
|
|
105
|
+
|
|
67
106
|
if reason is not None:
|
|
68
107
|
warnings.warn(
|
|
69
108
|
(
|
|
@@ -93,6 +132,26 @@ def _compute_cuped_statistics(
|
|
|
93
132
|
return p_value, standard_error
|
|
94
133
|
|
|
95
134
|
|
|
135
|
+
def _build_cuped_frame_from_prepared_context(
|
|
136
|
+
comparison_frame: pd.DataFrame,
|
|
137
|
+
user_id_column: str,
|
|
138
|
+
prepared_cuped_context: dict[str, object],
|
|
139
|
+
) -> tuple[pd.DataFrame | None, str | None]:
|
|
140
|
+
exp_values = prepared_cuped_context.get("exp_values")
|
|
141
|
+
if exp_values is None:
|
|
142
|
+
return None, f"experiment metric values are unavailable: {prepared_cuped_context.get('exp_error')}"
|
|
143
|
+
pre_values = prepared_cuped_context.get("pre_values")
|
|
144
|
+
if pre_values is None:
|
|
145
|
+
return None, f"pre-experiment metric values are unavailable: {prepared_cuped_context.get('pre_error')}"
|
|
146
|
+
|
|
147
|
+
cuped_frame = comparison_frame.copy()
|
|
148
|
+
cuped_frame = cuped_frame.join(exp_values, on=user_id_column).join(pre_values, on=user_id_column)
|
|
149
|
+
cuped_frame = cuped_frame.dropna(subset=["metric_exp", "metric_pre"]).reset_index(drop=True)
|
|
150
|
+
if cuped_frame.empty:
|
|
151
|
+
return None, "no overlapping non-missing experiment/pre-experiment observations"
|
|
152
|
+
return cuped_frame, None
|
|
153
|
+
|
|
154
|
+
|
|
96
155
|
def _build_cuped_frame(
|
|
97
156
|
df: pd.DataFrame,
|
|
98
157
|
pre_exp_metrics_df: pd.DataFrame,
|
{analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/ab_utils/metrics.py
RENAMED
|
@@ -29,6 +29,7 @@ from .cuped import (
|
|
|
29
29
|
_compute_cuped_p_value_from_frame,
|
|
30
30
|
_compute_cuped_statistics,
|
|
31
31
|
_compute_cuped_statistics_from_frame,
|
|
32
|
+
_prepare_cuped_context,
|
|
32
33
|
)
|
|
33
34
|
from .outliers import (
|
|
34
35
|
_apply_outliers_to_agg_ratio_components,
|
|
@@ -59,6 +60,7 @@ from .rows import (
|
|
|
59
60
|
_build_metric_definitions,
|
|
60
61
|
_build_metric_row,
|
|
61
62
|
_build_ratio_metric_row,
|
|
63
|
+
_prepare_metric_context,
|
|
62
64
|
)
|
|
63
65
|
from .stats import (
|
|
64
66
|
_both_present,
|
|
@@ -110,6 +112,8 @@ __all__ = [
|
|
|
110
112
|
"_build_ratio_valid_mask",
|
|
111
113
|
"_build_ratio_valid_mask_from_arrays",
|
|
112
114
|
"_build_value_outlier_mask",
|
|
115
|
+
"_prepare_cuped_context",
|
|
116
|
+
"_prepare_metric_context",
|
|
113
117
|
"_compute_agg_ratio_diff_standard_error",
|
|
114
118
|
"_compute_agg_ratio_group_stats",
|
|
115
119
|
"_compute_agg_ratio_group_stats_arrays",
|
{analytics_toolkit-1.3.7.3 → analytics_toolkit-1.3.8.2}/analytics_toolkit/ab_utils/parallel.py
RENAMED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import contextvars
|
|
4
|
+
import inspect
|
|
4
5
|
from collections.abc import Mapping
|
|
5
6
|
from contextlib import ExitStack
|
|
6
7
|
from concurrent.futures import Future, ThreadPoolExecutor, as_completed
|
|
@@ -20,6 +21,9 @@ from .api import compute_test_metrics
|
|
|
20
21
|
async_sql = sql_facade.async_sql
|
|
21
22
|
|
|
22
23
|
_SQL_DATAFRAME_FIELDS = frozenset({"df", "pre_exp_df", "pre_exp_metrics_df"})
|
|
24
|
+
_COMPUTE_TEST_METRICS_FIELDS = frozenset(
|
|
25
|
+
inspect.signature(compute_test_metrics).parameters
|
|
26
|
+
)
|
|
23
27
|
_DEFAULT_HARD_CONCURRENCY_CAP = 10
|
|
24
28
|
_CONCURRENCY_STATE: contextvars.ContextVar["_ConcurrencyState | None"] = (
|
|
25
29
|
contextvars.ContextVar("analytics_toolkit_ab_parallel_concurrency", default=None)
|
|
@@ -127,9 +131,11 @@ def parallel_compute_metrics_from_sql(
|
|
|
127
131
|
soft_concurrency_cap: int | None = None,
|
|
128
132
|
hard_concurrency_cap: int = _DEFAULT_HARD_CONCURRENCY_CAP,
|
|
129
133
|
progress: bool = False,
|
|
134
|
+
**metric_defaults: Any,
|
|
130
135
|
) -> dict[str, pd.DataFrame | str]:
|
|
131
136
|
"""Load SQL-backed task dataframes, then run ``parallel_compute_metrics``."""
|
|
132
137
|
_validate_start_comment("start_comment", start_comment)
|
|
138
|
+
metric_defaults = _validate_metric_defaults(metric_defaults)
|
|
133
139
|
task_defs = _validate_sql_tasks(tasks)
|
|
134
140
|
_validate_concurrency(concurrency)
|
|
135
141
|
_validate_optional_soft_concurrency_cap(soft_concurrency_cap)
|
|
@@ -191,7 +197,8 @@ def parallel_compute_metrics_from_sql(
|
|
|
191
197
|
sql_failures[name] = df_result
|
|
192
198
|
continue
|
|
193
199
|
|
|
194
|
-
metric_kwargs = dict(
|
|
200
|
+
metric_kwargs = dict(metric_defaults)
|
|
201
|
+
metric_kwargs.update(kwargs)
|
|
195
202
|
metric_kwargs["df"] = df_result
|
|
196
203
|
if pre_exp_sql is not None:
|
|
197
204
|
pre_exp_df_result = sql_results[_sql_read_task_name(name, "pre_exp_sql")]
|
|
@@ -424,6 +431,38 @@ def _validate_sql_task_spec(
|
|
|
424
431
|
return name, kwargs, sql, pre_exp_sql, start_comment
|
|
425
432
|
|
|
426
433
|
|
|
434
|
+
def _validate_metric_defaults(metric_defaults: Mapping[str, Any]) -> dict[str, Any]:
|
|
435
|
+
if not metric_defaults:
|
|
436
|
+
return {}
|
|
437
|
+
|
|
438
|
+
dataframe_fields = sorted(_SQL_DATAFRAME_FIELDS.intersection(metric_defaults))
|
|
439
|
+
if dataframe_fields:
|
|
440
|
+
fields = ", ".join(dataframe_fields)
|
|
441
|
+
raise ValueError(
|
|
442
|
+
"parallel_compute_metrics_from_sql top-level metric defaults cannot "
|
|
443
|
+
f"define SQL-backed dataframe field(s): {fields}."
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
supported_fields = _COMPUTE_TEST_METRICS_FIELDS - {
|
|
447
|
+
"df",
|
|
448
|
+
"pre_exp_metrics_df",
|
|
449
|
+
}
|
|
450
|
+
unexpected_fields = sorted(set(metric_defaults) - supported_fields)
|
|
451
|
+
if unexpected_fields:
|
|
452
|
+
quoted_fields = ", ".join(repr(field) for field in unexpected_fields)
|
|
453
|
+
if len(unexpected_fields) == 1:
|
|
454
|
+
raise TypeError(
|
|
455
|
+
"parallel_compute_metrics_from_sql() got an unexpected keyword "
|
|
456
|
+
f"argument {quoted_fields}"
|
|
457
|
+
)
|
|
458
|
+
raise TypeError(
|
|
459
|
+
"parallel_compute_metrics_from_sql() got unexpected keyword "
|
|
460
|
+
f"arguments: {quoted_fields}"
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
return dict(metric_defaults)
|
|
464
|
+
|
|
465
|
+
|
|
427
466
|
def _make_sql_read_task(
|
|
428
467
|
*,
|
|
429
468
|
name: str,
|
|
@@ -435,7 +474,7 @@ def _make_sql_read_task(
|
|
|
435
474
|
task = {
|
|
436
475
|
"name": _sql_read_task_name(name, field),
|
|
437
476
|
"type": "read",
|
|
438
|
-
"
|
|
477
|
+
"db_key": db,
|
|
439
478
|
"query": query,
|
|
440
479
|
}
|
|
441
480
|
if start_comment is not _MISSING:
|