diff --git a/.github/workflows/confidence.yml b/.github/workflows/confidence.yml index c4553ed..6c27a7b 100644 --- a/.github/workflows/confidence.yml +++ b/.github/workflows/confidence.yml @@ -13,7 +13,15 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.9', '3.10', '3.11', '3.12'] + include: + - python-version: '3.9' + tox-env: py39-min + - python-version: '3.10' + tox-env: py310 + - python-version: '3.11' + tox-env: py311 + - python-version: '3.12' + tox-env: py312 steps: - uses: actions/checkout@v4 @@ -26,9 +34,7 @@ jobs: with: enable-cache: true cache-dependency-glob: "**/pyproject.toml" - - name: Install dependencies - run: | - uv pip install --system -e ".[dev]" - uv pip install --system tox tox-gh-actions + - name: Install tox + run: uv pip install --system tox tox-uv - name: Test with tox - run: tox + run: tox -e ${{ matrix.tox-env }} diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index fb9d87f..f2fea36 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -23,12 +23,12 @@ jobs: uses: actions/setup-python@v5 with: python-version: '3.11' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install build + - name: Install uv + uses: astral-sh/setup-uv@v5 + - name: Install build tools + run: uv pip install --system build - name: Build package - run: python -m build + run: uv build - name: Publish a Python distribution to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: diff --git a/.gitignore b/.gitignore index edcb3af..411a53a 100644 --- a/.gitignore +++ b/.gitignore @@ -49,17 +49,6 @@ coverage.xml *.mo *.pot -# Django stuff: -*.log -local_settings.py - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - # Sphinx documentation docs/_build/ @@ -72,9 +61,6 @@ target/ # pyenv .python-version -# celery beat schedule file -celerybeat-schedule - # dotenv .env @@ -82,16 +68,13 @@ celerybeat-schedule venv/ ENV/ -# Spyder project settings -.spyderproject - -# Rope project settings -.ropeproject - .DS_store - .idea/ # uv uv.lock -.venv/ \ No newline at end of file +.venv/ + +.mypy_cache/ +.pytest_cache/ +.ruff_cache/ diff --git a/CLAUDE.md b/CLAUDE.md index fae842d..15b2947 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -11,7 +11,7 @@ Spotify Confidence is a Python library for A/B test analysis. It provides conven ### Setup ```bash # Install with development dependencies (including tox-uv) -uv pip install -e ".[dev]" +uv pip install -e . --group dev ``` ### Testing @@ -34,17 +34,14 @@ uv run tox ### Code Quality ```bash -# Format code with black (line length: 119) -uv run black spotify_confidence tests +# Run linting +uv run ruff check -# Check formatting without making changes -uv run black --check --diff spotify_confidence tests - -# Lint with flake8 (max line length: 120) -uv run flake8 spotify_confidence tests +# Run formatting +uv run ruff format # Run all quality checks (as done in CI) -uv run black --check --diff spotify_confidence tests && uv run flake8 spotify_confidence tests && uv run pytest +uv run ruff check && uv run ruff format && uv run pytest ``` ### Build @@ -151,7 +148,4 @@ The project uses `tox-uv` to leverage uv's fast package installation and environ ## Code Style -- Black formatting with 119 character line length -- Flake8 linting with max line length 120 -- Ignored flake8 rules: E203, E231, W503 -- Excluded from linting: `.venv`, `.tox`, `dist`, `build`, `scratch.py`, `confidence_dev` +Uses ruff linting and formatting. diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 86b3bd7..679b470 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -71,7 +71,7 @@ Ready to contribute? Here's how to set up `confidence` for local development. 3. Set up your development environment using uv:: $ uv venv - $ uv pip install -e ".[dev]" + $ uv pip install -e . --group dev This creates a virtual environment and installs the package in editable mode with all development dependencies. @@ -89,9 +89,9 @@ Ready to contribute? Here's how to set up `confidence` for local development. 6. When you're done making changes, check that your changes pass all quality checks:: - $ uv run black spotify_confidence tests --line-length 119 # Format code - $ uv run flake8 spotify_confidence tests # Lint code - $ uv run pytest # Run tests + $ uv run ruff format # Format code + $ uv run ruff check # Lint code + $ uv run pytest # Run tests To test across all supported Python versions (3.9, 3.10, 3.11, 3.12):: diff --git a/HISTORY.rst b/HISTORY.rst index 5549064..f68eaf9 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -2,6 +2,10 @@ History ======= +in development +-------------- +* Modernize infrastructure (pyproject.toml, uv, ruff, update dependencies) + 4.0.0 (2024-11-24) ------------------ * Update mininum requirement for Chartify to avoid cropping bug in `chrome-webdriver` diff --git a/Makefile b/Makefile deleted file mode 100644 index 8c0ff85..0000000 --- a/Makefile +++ /dev/null @@ -1,96 +0,0 @@ -.PHONY: clean clean-test clean-pyc clean-build docs help -.DEFAULT_GOAL := help -define BROWSER_PYSCRIPT -import os, webbrowser, sys -try: - from urllib import pathname2url -except: - from urllib.request import pathname2url - -webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) -endef -export BROWSER_PYSCRIPT - -define PRINT_HELP_PYSCRIPT -import re, sys - -for line in sys.stdin: - match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) - if match: - target, help = match.groups() - print("%-20s %s" % (target, help)) -endef -export PRINT_HELP_PYSCRIPT -BROWSER := python -c "$$BROWSER_PYSCRIPT" - -help: - @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) - -clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts - - -clean-build: ## remove build artifacts - rm -fr build/ - rm -fr dist/ - rm -fr .eggs/ - find . -name '*.egg-info' -exec rm -fr {} + - find . -name '*.egg' -exec rm -f {} + - -clean-pyc: ## remove Python file artifacts - find . -name '*.pyc' -exec rm -f {} + - find . -name '*.pyo' -exec rm -f {} + - find . -name '*~' -exec rm -f {} + - find . -name '__pycache__' -exec rm -fr {} + - -clean-test: ## remove test and coverage artifacts - rm -fr .tox/ - rm -f .coverage - rm -fr htmlcov/ - -format: ## format code with black - black spotify_confidence tests --line-length 119 - -lint: ## check style with flake8 - flake8 spotify_confidence tests - -test: ## run tests quickly with the default Python - python3 -m pytest - -coverage: ## check code coverage quickly with the default Python - coverage run --source spotify_confidence -m pytest - coverage report -m - coverage html - $(BROWSER) htmlcov/index.html - -# docs: ## generate Sphinx HTML documentation, including API docs -# rm -f docs/confidence.rst -# rm -f docs/modules.rst -# sphinx-apidoc -o docs/ confidence -# $(MAKE) -C docs clean -# $(MAKE) -C docs html -# $(BROWSER) docs/_build/html/index.html - -# servedocs: docs ## compile the docs watching for changes -# watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D . - -release-test: clean ## package and upload a release - python3 -m build - python3 -m twine upload --repository testpypi dist/* - -release-prod: clean ## package and upload a release - python3 -m build - python3 -m twine upload dist/* - -dist: clean ## builds source and wheel packagels - python3 -m build - ls -l dist - -install: clean ## install the package to the active Python's site-packages - pip install -e . - -install-test: clean - pip3 install --index-url https://test.pypi.org/simple/ spotify-confidence - -install-prod: clean - pip3 install spotify-confidence - diff --git a/pyproject.toml b/pyproject.toml index 57fda33..25cd3e9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,12 +24,11 @@ dependencies = [ "ipywidgets>=8.0.0", ] -[project.optional-dependencies] +[dependency-groups] dev = [ "build", "twine", - "black>=23.7.0", - "flake8>=6.0.0", + "ruff>=0.14.11", "tox>=4.0.0", "tox-uv>=1.0.0", "pytest>=7.0.0", @@ -47,9 +46,12 @@ where = ["."] include = ["spotify_confidence*"] namespaces = false -[tool.black] +[tool.ruff] line-length = 119 -target-version = ["py39", "py310", "py311", "py312"] +extend-exclude = ["examples/"] + +[tool.ruff.format] +quote-style = "double" [tool.pytest.ini_options] addopts = "-v -n auto --cov=spotify_confidence --cov-report=html --cov-report=xml --cov-report=term-missing" diff --git a/spotify_confidence/__init__.py b/spotify_confidence/__init__.py index d8e9f24..b6f88b0 100644 --- a/spotify_confidence/__init__.py +++ b/spotify_confidence/__init__.py @@ -13,17 +13,18 @@ # limitations under the License. from importlib.metadata import version as _version -from .analysis.bayesian.bayesian_models import BetaBinomial + from spotify_confidence.analysis.frequentist.chi_squared import ChiSquared +from spotify_confidence.analysis.frequentist.experiment import Experiment +from spotify_confidence.analysis.frequentist.sample_size_calculator import SampleSizeCalculator from spotify_confidence.analysis.frequentist.t_test import StudentsTTest from spotify_confidence.analysis.frequentist.z_test import ZTest from spotify_confidence.analysis.frequentist.z_test_linreg import ZTestLinreg -from spotify_confidence.analysis.frequentist.experiment import Experiment -from spotify_confidence.analysis.frequentist.sample_size_calculator import SampleSizeCalculator -from .samplesize.sample_size_calculator import SampleSize from . import examples +from .analysis.bayesian.bayesian_models import BetaBinomial from .options import options +from .samplesize.sample_size_calculator import SampleSize __version__ = _version("spotify_confidence") diff --git a/spotify_confidence/analysis/abstract_base_classes/confidence_abc.py b/spotify_confidence/analysis/abstract_base_classes/confidence_abc.py index e970f32..ce69749 100644 --- a/spotify_confidence/analysis/abstract_base_classes/confidence_abc.py +++ b/spotify_confidence/analysis/abstract_base_classes/confidence_abc.py @@ -13,14 +13,15 @@ # limitations under the License. from abc import ABC, abstractmethod -from typing import Union, Iterable, Tuple, List +from typing import Iterable, List, Tuple, Union from pandas import DataFrame from spotify_confidence.chartgrid import ChartGrid + +from ..constants import NIM_TYPE from .confidence_computer_abc import ConfidenceComputerABC from .confidence_grapher_abc import ConfidenceGrapherABC -from ..constants import NIM_TYPE class ConfidenceABC(ABC): diff --git a/spotify_confidence/analysis/abstract_base_classes/confidence_computer_abc.py b/spotify_confidence/analysis/abstract_base_classes/confidence_computer_abc.py index 112fa7f..541ea58 100644 --- a/spotify_confidence/analysis/abstract_base_classes/confidence_computer_abc.py +++ b/spotify_confidence/analysis/abstract_base_classes/confidence_computer_abc.py @@ -13,7 +13,7 @@ # limitations under the License. from abc import ABC, abstractmethod -from typing import Union, Iterable, List, Tuple +from typing import Iterable, List, Tuple, Union from pandas import DataFrame diff --git a/spotify_confidence/analysis/abstract_base_classes/confidence_grapher_abc.py b/spotify_confidence/analysis/abstract_base_classes/confidence_grapher_abc.py index 737787d..73f0393 100644 --- a/spotify_confidence/analysis/abstract_base_classes/confidence_grapher_abc.py +++ b/spotify_confidence/analysis/abstract_base_classes/confidence_grapher_abc.py @@ -13,11 +13,12 @@ # limitations under the License. from abc import ABC, abstractmethod -from typing import Union, Iterable +from typing import Iterable, Union from pandas import DataFrame from spotify_confidence.chartgrid import ChartGrid + from ..constants import NIM_TYPE diff --git a/spotify_confidence/analysis/bayesian/bayesian_base.py b/spotify_confidence/analysis/bayesian/bayesian_base.py index 13f666e..6177cbc 100644 --- a/spotify_confidence/analysis/bayesian/bayesian_base.py +++ b/spotify_confidence/analysis/bayesian/bayesian_base.py @@ -12,18 +12,18 @@ # See the License for the specific language governing permissions and # limitations under the License. -from abc import ABCMeta, abstractmethod -from functools import wraps import types import warnings +from abc import ABCMeta, abstractmethod +from functools import wraps import chartify import numpy as np import pandas as pd -from spotify_confidence.options import options -from spotify_confidence.chartgrid import ChartGrid from spotify_confidence.analysis.confidence_utils import de_list_if_length_one +from spotify_confidence.chartgrid import ChartGrid +from spotify_confidence.options import options # warnings.simplefilter("once") @@ -77,9 +77,7 @@ def wrapper(*args, **kwargs): confidence.options.set_option('randomization_seed', {}) - """.format( - INITIAL_RANDOMIZATION_SEED - ) + """.format(INITIAL_RANDOMIZATION_SEED) warnings.warn(randomization_warning_message) option_seed = np_seed np.random.seed(option_seed) @@ -136,9 +134,7 @@ def _validate_data(self): ): raise TypeError( """`ordinal_group_column` is type `{}`. - Must be number or datetime type.""".format( - ordinal_column_type - ) + Must be number or datetime type.""".format(ordinal_column_type) ) @classmethod @@ -455,9 +451,7 @@ def _validate_levels(level_df, remaining_groups, level): Must supply a level within the ungrouped dimensions: {} Valid levels: {} - """.format( - level, remaining_groups, list(level_df.groupby(remaining_groups).groups.keys()) - ) + """.format(level, remaining_groups, list(level_df.groupby(remaining_groups).groups.keys())) ) def _groupby_iterator(self, input_function, groupby, **kwargs): diff --git a/spotify_confidence/analysis/bayesian/bayesian_models.py b/spotify_confidence/analysis/bayesian/bayesian_models.py index cd3e93a..722728f 100644 --- a/spotify_confidence/analysis/bayesian/bayesian_models.py +++ b/spotify_confidence/analysis/bayesian/bayesian_models.py @@ -21,11 +21,10 @@ from spotify_confidence.analysis.bayesian.bayesian_base import ( BaseTest, - randomization_warning_decorator, - format_str_precision, axis_format_precision, + format_str_precision, + randomization_warning_decorator, ) - from spotify_confidence.analysis.confidence_utils import de_list_if_length_one diff --git a/spotify_confidence/analysis/confidence_utils.py b/spotify_confidence/analysis/confidence_utils.py index 29f241a..a7545a9 100644 --- a/spotify_confidence/analysis/confidence_utils.py +++ b/spotify_confidence/analysis/confidence_utils.py @@ -14,10 +14,10 @@ from collections import OrderedDict from concurrent.futures.thread import ThreadPoolExecutor -from typing import Union, Iterable, Tuple, List +from typing import Iterable, List, Tuple, Union import numpy as np -from pandas import DataFrame, concat, Series +from pandas import DataFrame, Series, concat from scipy.stats import norm from spotify_confidence.analysis.constants import ( @@ -106,9 +106,7 @@ def validate_levels(df: DataFrame, level_columns: Union[str, Iterable], levels: Must supply a level within the ungrouped dimensions: {} Valid levels: {} - """.format( - level, level_columns, list(df.groupby(level_columns).groups.keys()) - ) + """.format(level, level_columns, list(df.groupby(level_columns).groups.keys())) ) @@ -162,9 +160,7 @@ def validate_data(df: DataFrame, columns_that_must_exist, group_columns: Iterabl if not np.issubdtype(ordinal_column_type, np.number) and not issubclass(ordinal_column_type, np.datetime64): raise TypeError( """`ordinal_group_column` is type `{}`. - Must be number or datetime type.""".format( - ordinal_column_type - ) + Must be number or datetime type.""".format(ordinal_column_type) ) diff --git a/spotify_confidence/analysis/constants.py b/spotify_confidence/analysis/constants.py index 399ccfe..55c6d1f 100644 --- a/spotify_confidence/analysis/constants.py +++ b/spotify_confidence/analysis/constants.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Tuple, Dict, Union +from typing import Dict, Tuple, Union NUMERATOR = "numerator" NUMERATOR_SUM_OF_SQUARES = "numerator_sum_of_squares" diff --git a/spotify_confidence/analysis/frequentist/chartify_grapher.py b/spotify_confidence/analysis/frequentist/chartify_grapher.py index 874a5da..ead6717 100644 --- a/spotify_confidence/analysis/frequentist/chartify_grapher.py +++ b/spotify_confidence/analysis/frequentist/chartify_grapher.py @@ -12,41 +12,41 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Union, Iterable, Tuple +from typing import Iterable, Tuple, Union import numpy as np from bokeh.models import tools from chartify import Chart from pandas import DataFrame, concat +from ...chartgrid import ChartGrid from ..abstract_base_classes.confidence_grapher_abc import ConfidenceGrapherABC from ..confidence_utils import ( - axis_format_precision, add_color_column, - get_remaning_groups, + axis_format_precision, + de_list_if_length_one, get_all_group_columns, - listify, + get_remaning_groups, level2str, + listify, to_finite, - de_list_if_length_one, ) from ..constants import ( - POINT_ESTIMATE, - ORIGINAL_POINT_ESTIMATE, - DIFFERENCE, - CI_LOWER, - CI_UPPER, - P_VALUE, ADJUSTED_LOWER, - ADJUSTED_UPPER, ADJUSTED_P, - NULL_HYPOTHESIS, + ADJUSTED_UPPER, + CI_LOWER, + CI_UPPER, + DIFFERENCE, NIM, NIM_TYPE, + NULL_HYPOTHESIS, + ORIGINAL_POINT_ESTIMATE, + P_VALUE, + POINT_ESTIMATE, PREFERENCE, SFX1, ) -from ...chartgrid import ChartGrid class ChartifyGrapher(ConfidenceGrapherABC): @@ -627,7 +627,7 @@ def add_tools( + [ ( ("adjusted " if use_adjusted_intervals else "") + "confidence interval", - f"(@{{{LOWER}}}{{{axis_format}}}," f" @{{{UPPER}}}{{{axis_format}}})", + f"(@{{{LOWER}}}{{{axis_format}}}, @{{{UPPER}}}{{{axis_format}}})", ) ] + p_value_tool_tip diff --git a/spotify_confidence/analysis/frequentist/chi_squared.py b/spotify_confidence/analysis/frequentist/chi_squared.py index 3a6ab76..aaaf3e8 100644 --- a/spotify_confidence/analysis/frequentist/chi_squared.py +++ b/spotify_confidence/analysis/frequentist/chi_squared.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Union, Iterable +from typing import Iterable, Union from pandas import DataFrame diff --git a/spotify_confidence/analysis/frequentist/confidence_computers/__init__.py b/spotify_confidence/analysis/frequentist/confidence_computers/__init__.py index e44d0a8..36e9054 100644 --- a/spotify_confidence/analysis/frequentist/confidence_computers/__init__.py +++ b/spotify_confidence/analysis/frequentist/confidence_computers/__init__.py @@ -1,9 +1,17 @@ -from spotify_confidence.analysis.constants import CHI2, TTEST, ZTEST, BOOTSTRAP, ZTESTLINREG +from spotify_confidence.analysis.constants import BOOTSTRAP, CHI2, TTEST, ZTEST, ZTESTLINREG +from spotify_confidence.analysis.frequentist.confidence_computers import ( + bootstrap_computer as bootstrap_computer, +) from spotify_confidence.analysis.frequentist.confidence_computers import ( chi_squared_computer as chi_squared_computer, +) +from spotify_confidence.analysis.frequentist.confidence_computers import ( t_test_computer as t_test_computer, +) +from spotify_confidence.analysis.frequentist.confidence_computers import ( z_test_computer as z_test_computers, - bootstrap_computer as bootstrap_computer, +) +from spotify_confidence.analysis.frequentist.confidence_computers import ( z_test_linreg_computer as z_test_linreg_computer, ) diff --git a/spotify_confidence/analysis/frequentist/confidence_computers/bootstrap_computer.py b/spotify_confidence/analysis/frequentist/confidence_computers/bootstrap_computer.py index f76ff93..5d8240f 100644 --- a/spotify_confidence/analysis/frequentist/confidence_computers/bootstrap_computer.py +++ b/spotify_confidence/analysis/frequentist/confidence_computers/bootstrap_computer.py @@ -1,9 +1,9 @@ -from typing import Tuple, Dict +from typing import Dict, Tuple import numpy as np from pandas import DataFrame, Series -from spotify_confidence.analysis.constants import CI_LOWER, CI_UPPER, SFX1, SFX2, BOOTSTRAPS, INTERVAL_SIZE +from spotify_confidence.analysis.constants import BOOTSTRAPS, CI_LOWER, CI_UPPER, INTERVAL_SIZE, SFX1, SFX2 def point_estimate(df: DataFrame, **kwargs: Dict[str, str]) -> float: @@ -16,7 +16,7 @@ def variance(df: Series, **kwargs: Dict[str, str]) -> float: variance = df[bootstrap_samples].map(lambda a: a.var()) if (variance < 0).any(): - raise ValueError("Computed variance is negative. " "Please check your inputs.") + raise ValueError("Computed variance is negative. Please check your inputs.") return variance diff --git a/spotify_confidence/analysis/frequentist/confidence_computers/chi_squared_computer.py b/spotify_confidence/analysis/frequentist/confidence_computers/chi_squared_computer.py index 0a1fbb7..d6e2c7b 100644 --- a/spotify_confidence/analysis/frequentist/confidence_computers/chi_squared_computer.py +++ b/spotify_confidence/analysis/frequentist/confidence_computers/chi_squared_computer.py @@ -1,20 +1,20 @@ -from typing import Tuple, Dict +from typing import Dict, Tuple import numpy as np from pandas import DataFrame, Series -from statsmodels.stats.proportion import proportion_confint, proportions_chisquare, confint_proportions_2indep +from statsmodels.stats.proportion import confint_proportions_2indep, proportion_confint, proportions_chisquare from spotify_confidence.analysis.confidence_utils import power_calculation from spotify_confidence.analysis.constants import ( - NUMERATOR, + CI_LOWER, + CI_UPPER, DENOMINATOR, INTERVAL_SIZE, + NUMERATOR, POINT_ESTIMATE, - VARIANCE, - CI_LOWER, - CI_UPPER, SFX1, SFX2, + VARIANCE, ) @@ -29,7 +29,7 @@ def point_estimate(df: DataFrame, **kwargs: Dict[str, str]) -> float: def variance(df: DataFrame, **kwargs: Dict[str, str]) -> Series: variance = df[POINT_ESTIMATE] * (1 - df[POINT_ESTIMATE]) if (variance < 0).any(): - raise ValueError(f"Computed variance is negative: {variance}. " "Please check your inputs.") + raise ValueError(f"Computed variance is negative: {variance}. Please check your inputs.") return variance diff --git a/spotify_confidence/analysis/frequentist/confidence_computers/confidence_computer.py b/spotify_confidence/analysis/frequentist/confidence_computers/confidence_computer.py index 4ea6105..fc355ba 100644 --- a/spotify_confidence/analysis/frequentist/confidence_computers/confidence_computer.py +++ b/spotify_confidence/analysis/frequentist/confidence_computers/confidence_computer.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Union, Iterable, List, Tuple, Dict +from typing import Dict, Iterable, List, Tuple, Union import numpy as np from numpy import isnan @@ -21,82 +21,82 @@ from spotify_confidence.analysis.abstract_base_classes.confidence_computer_abc import ConfidenceComputerABC from spotify_confidence.analysis.confidence_utils import ( - get_remaning_groups, - validate_levels, - level2str, - listify, - validate_and_rename_columns, drop_and_rename_columns, get_all_categorical_group_columns, get_all_group_columns, - validate_data, - remove_group_columns, + get_remaning_groups, groupbyApplyParallel, + level2str, + listify, + remove_group_columns, reset_named_indices, + validate_and_rename_columns, + validate_data, + validate_levels, ) from spotify_confidence.analysis.constants import ( - NUMERATOR, - NUMERATOR_SUM_OF_SQUARES, - DENOMINATOR, + ABSOLUTE, + ADJUSTED_ALPHA_POWER_SAMPLE_SIZE, + ADJUSTED_LOWER, + ADJUSTED_P, + ADJUSTED_POWER, + ADJUSTED_UPPER, + ALTERNATIVE_HYPOTHESIS, + BOOTSTRAP, BOOTSTRAPS, - INTERVAL_SIZE, - POINT_ESTIMATE, + CHI2, + CI_LOWER, + CI_UPPER, + CORRECTION_METHOD, + CORRECTION_METHODS, + DENOMINATOR, + DIFFERENCE, + FEATURE, + FEATURE_CROSS, + FEATURE_SUMSQ, FINAL_EXPECTED_SAMPLE_SIZE, - ORDINAL_GROUP_COLUMN, + INTERVAL_SIZE, + IS_SIGNIFICANT, MDE, METHOD, - CORRECTION_METHOD, - ABSOLUTE, - VARIANCE, + NIM, + NIM_COLUMN_DEFAULT, + NIM_TYPE, + NULL_HYPOTHESIS, NUMBER_OF_COMPARISONS, - FEATURE, - FEATURE_SUMSQ, - FEATURE_CROSS, - CI_LOWER, - CI_UPPER, - DIFFERENCE, + NUMERATOR, + NUMERATOR_SUM_OF_SQUARES, + ORDINAL_GROUP_COLUMN, + ORIGINAL_POINT_ESTIMATE, + ORIGINAL_VARIANCE, P_VALUE, - SFX1, - SFX2, - STD_ERR, - ADJUSTED_ALPHA_POWER_SAMPLE_SIZE, + POINT_ESTIMATE, POWER, POWERED_EFFECT, - ADJUSTED_POWER, - ADJUSTED_P, - ADJUSTED_LOWER, - ADJUSTED_UPPER, - IS_SIGNIFICANT, - REQUIRED_SAMPLE_SIZE, - REQUIRED_SAMPLE_SIZE_METRIC, - NULL_HYPOTHESIS, - ALTERNATIVE_HYPOTHESIS, - NIM, PREFERENCE, - PREFERENCE_TEST, - TWO_SIDED, PREFERENCE_DICT, - CORRECTION_METHODS, - BOOTSTRAP, - CHI2, + PREFERENCE_TEST, + PREFERRED_DIRECTION_COLUMN_DEFAULT, + REQUIRED_SAMPLE_SIZE, + REQUIRED_SAMPLE_SIZE_METRIC, + SFX1, + SFX2, + STD_ERR, TTEST, + TWO_SIDED, + VARIANCE, + VARIANCE_REDUCTION, ZTEST, - NIM_TYPE, - NIM_COLUMN_DEFAULT, - PREFERRED_DIRECTION_COLUMN_DEFAULT, ZTESTLINREG, - ORIGINAL_POINT_ESTIMATE, - ORIGINAL_VARIANCE, - VARIANCE_REDUCTION, ) from spotify_confidence.analysis.frequentist.confidence_computers import confidence_computers from spotify_confidence.analysis.frequentist.multiple_comparison import ( - get_num_comparisons, add_adjusted_p_and_is_significant, + add_adjusted_power, add_ci, - set_alpha_and_adjust_preference, + get_num_comparisons, get_preference, - add_adjusted_power, + set_alpha_and_adjust_preference, ) from spotify_confidence.analysis.frequentist.nims_and_mdes import ( add_nim_input_columns_from_tuple_or_dict, @@ -413,7 +413,7 @@ def assign_total_denominator(df, groupby): level=self._sufficient_statistics[level_columns].agg(level2str, axis="columns") ) .pipe(assign_total_denominator, groupby) - .query(f"level in {[l1 for l1,l2 in levels] + [l2 for l1,l2 in levels]}") + .query(f"level in {[l1 for l1, l2 in levels] + [l2 for l1, l2 in levels]}") .pipe(lambda df: df if groupby == [] else df.set_index(groupby)) .pipe( self._create_comparison_df, diff --git a/spotify_confidence/analysis/frequentist/confidence_computers/sample_size_computer.py b/spotify_confidence/analysis/frequentist/confidence_computers/sample_size_computer.py index a35bef9..80b45f9 100644 --- a/spotify_confidence/analysis/frequentist/confidence_computers/sample_size_computer.py +++ b/spotify_confidence/analysis/frequentist/confidence_computers/sample_size_computer.py @@ -12,62 +12,62 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Union, Iterable, List, Tuple, Dict +from typing import Dict, Iterable, List, Tuple, Union import numpy as np from pandas import DataFrame, Series from scipy import stats as st from spotify_confidence.analysis.confidence_utils import ( + de_list_if_length_one, get_all_categorical_group_columns, get_all_group_columns, - validate_data, - remove_group_columns, groupbyApplyParallel, is_non_inferiority, + remove_group_columns, reset_named_indices, - de_list_if_length_one, + validate_data, ) from spotify_confidence.analysis.constants import ( - INTERVAL_SIZE, - POINT_ESTIMATE, - FINAL_EXPECTED_SAMPLE_SIZE, - MDE, - CORRECTION_METHOD, - VARIANCE, - NUMBER_OF_COMPARISONS, - TREATMENT_WEIGHTS, - IS_BINARY, - CI_LOWER, - CI_UPPER, - DIFFERENCE, - SFX1, ADJUSTED_ALPHA_POWER_SAMPLE_SIZE, - POWER, - POWERED_EFFECT, - ADJUSTED_POWER, ADJUSTED_LOWER, + ADJUSTED_POWER, ADJUSTED_UPPER, - REQUIRED_SAMPLE_SIZE_METRIC, - OPTIMAL_KAPPA, - OPTIMAL_WEIGHTS, - CI_WIDTH, - NULL_HYPOTHESIS, ALTERNATIVE_HYPOTHESIS, + CI_LOWER, + CI_UPPER, + CI_WIDTH, + CORRECTION_METHOD, + CORRECTION_METHODS, + DIFFERENCE, + FINAL_EXPECTED_SAMPLE_SIZE, + INTERVAL_SIZE, + IS_BINARY, + MDE, NIM, + NULL_HYPOTHESIS, + NUMBER_OF_COMPARISONS, + OPTIMAL_KAPPA, + OPTIMAL_WEIGHTS, + ORIGINAL_POINT_ESTIMATE, + ORIGINAL_VARIANCE, + POINT_ESTIMATE, + POWER, + POWERED_EFFECT, PREFERENCE_TEST, + REQUIRED_SAMPLE_SIZE_METRIC, + SFX1, + TREATMENT_WEIGHTS, TWO_SIDED, - CORRECTION_METHODS, + VARIANCE, ZTEST, - ORIGINAL_POINT_ESTIMATE, - ORIGINAL_VARIANCE, ) from spotify_confidence.analysis.frequentist.confidence_computers import confidence_computers from spotify_confidence.analysis.frequentist.multiple_comparison import ( + add_adjusted_power, get_num_comparisons, - set_alpha_and_adjust_preference, get_preference, - add_adjusted_power, + set_alpha_and_adjust_preference, ) from spotify_confidence.analysis.frequentist.nims_and_mdes import ( add_nims_and_mdes, diff --git a/spotify_confidence/analysis/frequentist/confidence_computers/t_test_computer.py b/spotify_confidence/analysis/frequentist/confidence_computers/t_test_computer.py index dc75b54..59652fb 100644 --- a/spotify_confidence/analysis/frequentist/confidence_computers/t_test_computer.py +++ b/spotify_confidence/analysis/frequentist/confidence_computers/t_test_computer.py @@ -1,4 +1,4 @@ -from typing import Tuple, Dict +from typing import Dict, Tuple import numpy as np from pandas import DataFrame, Series @@ -6,21 +6,21 @@ from spotify_confidence.analysis.confidence_utils import power_calculation from spotify_confidence.analysis.constants import ( - NUMERATOR, - NUMERATOR_SUM_OF_SQUARES, + CI_LOWER, + CI_UPPER, DENOMINATOR, + DIFFERENCE, INTERVAL_SIZE, + NULL_HYPOTHESIS, + NUMERATOR, + NUMERATOR_SUM_OF_SQUARES, POINT_ESTIMATE, - CI_LOWER, - CI_UPPER, - VARIANCE, - TWO_SIDED, + PREFERENCE_TEST, SFX1, SFX2, STD_ERR, - PREFERENCE_TEST, - NULL_HYPOTHESIS, - DIFFERENCE, + TWO_SIDED, + VARIANCE, ) @@ -44,7 +44,7 @@ def variance(df: DataFrame, **kwargs: Dict[str, str]) -> float: else: variance = (df[numerator_sumsq] - np.power(df[numerator], 2) / df[denominator]) / (df[denominator] - 1) if (variance < 0).any(): - raise ValueError("Computed variance is negative. " "Please check your inputs.") + raise ValueError("Computed variance is negative. Please check your inputs.") return variance diff --git a/spotify_confidence/analysis/frequentist/confidence_computers/z_test_computer.py b/spotify_confidence/analysis/frequentist/confidence_computers/z_test_computer.py index 7f0395d..0f5effd 100644 --- a/spotify_confidence/analysis/frequentist/confidence_computers/z_test_computer.py +++ b/spotify_confidence/analysis/frequentist/confidence_computers/z_test_computer.py @@ -1,4 +1,4 @@ -from typing import Tuple, Union, Dict +from typing import Dict, Tuple, Union import numpy as np from pandas import DataFrame, Series @@ -14,37 +14,37 @@ from spotify_confidence.analysis.confidence_utils import power_calculation from spotify_confidence.analysis.constants import ( - NUMERATOR, - NUMERATOR_SUM_OF_SQUARES, - DENOMINATOR, - INTERVAL_SIZE, - FINAL_EXPECTED_SAMPLE_SIZE, - ORDINAL_GROUP_COLUMN, - POINT_ESTIMATE, - CI_LOWER, - CI_UPPER, + ADJUSTED_ALPHA, ADJUSTED_LOWER, ADJUSTED_UPPER, - VARIANCE, - NUMBER_OF_COMPARISONS, - TWO_SIDED, - SFX2, - SFX1, - STD_ERR, - PREFERENCE_TEST, - NULL_HYPOTHESIS, - DIFFERENCE, ALPHA, - IS_SIGNIFICANT, + CI_LOWER, + CI_UPPER, + DENOMINATOR, + DIFFERENCE, + FINAL_EXPECTED_SAMPLE_SIZE, HOLM, - SPOT_1_HOLM, HOMMEL, + INTERVAL_SIZE, + IS_SIGNIFICANT, + NIM, + NULL_HYPOTHESIS, + NUMBER_OF_COMPARISONS, + NUMERATOR, + NUMERATOR_SUM_OF_SQUARES, + ORDINAL_GROUP_COLUMN, + ORIGINAL_POINT_ESTIMATE, + POINT_ESTIMATE, + PREFERENCE_TEST, + SFX1, + SFX2, SIMES_HOCHBERG, + SPOT_1_HOLM, SPOT_1_HOMMEL, SPOT_1_SIMES_HOCHBERG, - NIM, - ADJUSTED_ALPHA, - ORIGINAL_POINT_ESTIMATE, + STD_ERR, + TWO_SIDED, + VARIANCE, ) from spotify_confidence.analysis.frequentist.sequential_bound_solver import bounds @@ -73,7 +73,7 @@ def variance(df: DataFrame, **kwargs: Dict[str, str]) -> float: else: variance = (df[numerator_sumsq] - np.power(df[numerator], 2) / df[denominator]) / (df[denominator] - 1) if (variance < 0).any(): - raise ValueError("Computed variance is negative. " "Please check your inputs.") + raise ValueError("Computed variance is negative. Please check your inputs.") return variance diff --git a/spotify_confidence/analysis/frequentist/confidence_computers/z_test_linreg_computer.py b/spotify_confidence/analysis/frequentist/confidence_computers/z_test_linreg_computer.py index cc8f1da..9935c11 100644 --- a/spotify_confidence/analysis/frequentist/confidence_computers/z_test_linreg_computer.py +++ b/spotify_confidence/analysis/frequentist/confidence_computers/z_test_linreg_computer.py @@ -1,17 +1,17 @@ from functools import reduce -from typing import Union, Dict +from typing import Dict, Union import numpy as np from pandas import DataFrame, Series -from spotify_confidence.analysis.confidence_utils import unlist, dfmatmul +from spotify_confidence.analysis.confidence_utils import dfmatmul, unlist from spotify_confidence.analysis.constants import ( - REGRESSION_PARAM, + DENOMINATOR, FEATURE, - FEATURE_SUMSQ, FEATURE_CROSS, + FEATURE_SUMSQ, NUMERATOR, - DENOMINATOR, + REGRESSION_PARAM, ) from spotify_confidence.analysis.frequentist.confidence_computers import z_test_computer @@ -94,7 +94,7 @@ def variance(df: DataFrame, **kwargs) -> Series: if kwargs[FEATURE] in df: computed_variances = variance1 + df.apply(lin_reg_variance_delta, axis=1, **kwargs) if (computed_variances < 0).any(): - raise ValueError("Computed variance is negative, please check sufficient " "statistics.") + raise ValueError("Computed variance is negative, please check sufficient statistics.") return computed_variances else: return variance1 diff --git a/spotify_confidence/analysis/frequentist/experiment.py b/spotify_confidence/analysis/frequentist/experiment.py index 276dad7..7a78ef1 100644 --- a/spotify_confidence/analysis/frequentist/experiment.py +++ b/spotify_confidence/analysis/frequentist/experiment.py @@ -12,24 +12,25 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Union, Iterable, Tuple, Dict, List +from typing import Dict, Iterable, List, Tuple, Union from pandas import DataFrame from spotify_confidence.analysis.frequentist.confidence_computers.confidence_computer import ConfidenceComputer -from .chartify_grapher import ChartifyGrapher + +from ...chartgrid import ChartGrid from ..abstract_base_classes.confidence_abc import ConfidenceABC from ..abstract_base_classes.confidence_computer_abc import ConfidenceComputerABC from ..abstract_base_classes.confidence_grapher_abc import ConfidenceGrapherABC from ..confidence_utils import ( - validate_categorical_columns, - listify, get_all_categorical_group_columns, get_all_group_columns, + listify, + validate_categorical_columns, ) -from ..constants import BONFERRONI, NIM_TYPE, METHODS +from ..constants import BONFERRONI, METHODS, NIM_TYPE from ..frequentist.sample_ratio_test import sample_ratio_test -from ...chartgrid import ChartGrid +from .chartify_grapher import ChartifyGrapher class Experiment(ConfidenceABC): diff --git a/spotify_confidence/analysis/frequentist/multiple_comparison.py b/spotify_confidence/analysis/frequentist/multiple_comparison.py index 65cdc46..97c855f 100644 --- a/spotify_confidence/analysis/frequentist/multiple_comparison.py +++ b/spotify_confidence/analysis/frequentist/multiple_comparison.py @@ -1,58 +1,58 @@ from _warnings import warn -from typing import Iterable, Dict +from typing import Dict, Iterable from pandas import DataFrame from statsmodels.stats.multitest import multipletests from spotify_confidence.analysis.confidence_utils import groupbyApplyParallel from spotify_confidence.analysis.constants import ( + ADJUSTED_ALPHA, + ADJUSTED_ALPHA_POWER_SAMPLE_SIZE, + ADJUSTED_LOWER, + ADJUSTED_P, + ADJUSTED_POWER, + ADJUSTED_UPPER, + ALPHA, BONFERRONI, + BONFERRONI_DO_NOT_COUNT_NON_INFERIORITY, BONFERRONI_ONLY_COUNT_TWOSIDED, - PREFERENCE_TEST, - TWO_SIDED, - HOLM, - HOMMEL, - SIMES_HOCHBERG, - SIDAK, - HOLM_SIDAK, + CI_LOWER, + CI_UPPER, + CORRECTION_METHOD, + CORRECTION_METHODS_THAT_REQUIRE_METRIC_INFO, FDR_BH, FDR_BY, FDR_TSBH, FDR_TSBKY, - BONFERRONI_DO_NOT_COUNT_NON_INFERIORITY, + FINAL_EXPECTED_SAMPLE_SIZE, + HOLM, + HOLM_SIDAK, + HOMMEL, + INTERVAL_SIZE, + IS_SIGNIFICANT, + METHOD, + NIM, + NUMBER_OF_COMPARISONS, + ORDINAL_GROUP_COLUMN, + P_VALUE, + POWER, + PREFERENCE, + PREFERENCE_TEST, + SIDAK, + SIMES_HOCHBERG, SPOT_1, - SPOT_1_HOLM, - SPOT_1_HOMMEL, - SPOT_1_SIMES_HOCHBERG, - SPOT_1_SIDAK, - SPOT_1_HOLM_SIDAK, SPOT_1_FDR_BH, SPOT_1_FDR_BY, SPOT_1_FDR_TSBH, SPOT_1_FDR_TSBKY, - NIM, - NUMBER_OF_COMPARISONS, - FINAL_EXPECTED_SAMPLE_SIZE, - ORDINAL_GROUP_COLUMN, - CORRECTION_METHOD, - METHOD, - IS_SIGNIFICANT, - P_VALUE, - ADJUSTED_ALPHA, - ADJUSTED_P, - ALPHA, - INTERVAL_SIZE, + SPOT_1_HOLM, + SPOT_1_HOLM_SIDAK, + SPOT_1_HOMMEL, + SPOT_1_SIDAK, + SPOT_1_SIMES_HOCHBERG, + TWO_SIDED, ZTEST, ZTESTLINREG, - CI_LOWER, - CI_UPPER, - ADJUSTED_LOWER, - ADJUSTED_UPPER, - PREFERENCE, - ADJUSTED_ALPHA_POWER_SAMPLE_SIZE, - CORRECTION_METHODS_THAT_REQUIRE_METRIC_INFO, - ADJUSTED_POWER, - POWER, ) from spotify_confidence.analysis.frequentist.confidence_computers import confidence_computers diff --git a/spotify_confidence/analysis/frequentist/nims_and_mdes.py b/spotify_confidence/analysis/frequentist/nims_and_mdes.py index 7bfc20f..d186e38 100644 --- a/spotify_confidence/analysis/frequentist/nims_and_mdes.py +++ b/spotify_confidence/analysis/frequentist/nims_and_mdes.py @@ -3,17 +3,17 @@ from spotify_confidence.analysis.confidence_utils import listify from spotify_confidence.analysis.constants import ( - NIM_TYPE, - NIM_COLUMN_DEFAULT, - PREFERRED_DIRECTION_COLUMN_DEFAULT, - ORIGINAL_POINT_ESTIMATE, - TWO_SIDED, - INCREASE_PREFFERED, + ALTERNATIVE_HYPOTHESIS, DECREASE_PREFFERED, + INCREASE_PREFFERED, NIM, - PREFERENCE, + NIM_COLUMN_DEFAULT, + NIM_TYPE, NULL_HYPOTHESIS, - ALTERNATIVE_HYPOTHESIS, + ORIGINAL_POINT_ESTIMATE, + PREFERENCE, + PREFERRED_DIRECTION_COLUMN_DEFAULT, + TWO_SIDED, ) @@ -66,7 +66,7 @@ def _set_nims_and_mdes(grp: DataFrame) -> DataFrame: preference = "smaller" signed_mde = None if mde_is_na else -mde * grp[ORIGINAL_POINT_ESTIMATE] else: - raise ValueError(f"{input_preference.lower()} not in " f"{[INCREASE_PREFFERED, DECREASE_PREFFERED]}") + raise ValueError(f"{input_preference.lower()} not in {[INCREASE_PREFFERED, DECREASE_PREFFERED]}") return ( grp.assign(**{NIM: nim}) diff --git a/spotify_confidence/analysis/frequentist/sample_ratio_test.py b/spotify_confidence/analysis/frequentist/sample_ratio_test.py index 0a89fa3..ddba3ed 100644 --- a/spotify_confidence/analysis/frequentist/sample_ratio_test.py +++ b/spotify_confidence/analysis/frequentist/sample_ratio_test.py @@ -12,10 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Dict, Iterable, Tuple + import numpy as np from pandas import DataFrame from scipy.stats import chi2 -from typing import Dict, Tuple, Iterable def sample_ratio_test( @@ -44,7 +45,7 @@ def sample_ratio_test( if not isinstance(expected_proportions, dict): raise TypeError( - "`expected_proportions` must be a dict with " "groupings as keys and expected proportions " "as values" + "`expected_proportions` must be a dict with groupings as keys and expected proportions as values" ) elif not np.allclose(sum(expected_proportions.values()), 1.0): raise ValueError("proportions must sum to one") @@ -53,7 +54,7 @@ def sample_ratio_test( all_groups = list(df.groupby(all_group_columns, sort=False).groups.keys()) if set(all_groups) != set(expected_proportions.keys()): - raise ValueError(f"`expected_proportion` keys must match groupings in the " f"order {all_group_columns}") + raise ValueError(f"`expected_proportion` keys must match groupings in the order {all_group_columns}") n_tot = df[denominator].sum() diff --git a/spotify_confidence/analysis/frequentist/sample_size_calculator.py b/spotify_confidence/analysis/frequentist/sample_size_calculator.py index e550994..57fa031 100644 --- a/spotify_confidence/analysis/frequentist/sample_size_calculator.py +++ b/spotify_confidence/analysis/frequentist/sample_size_calculator.py @@ -1,12 +1,13 @@ -from typing import Union, Iterable, Tuple +from typing import Iterable, Tuple, Union from pandas import DataFrame from spotify_confidence.analysis.frequentist.confidence_computers.sample_size_computer import SampleSizeComputer + from ..confidence_utils import ( listify, ) -from ..constants import BONFERRONI, ZTEST, METHOD_COLUMN_NAME +from ..constants import BONFERRONI, METHOD_COLUMN_NAME, ZTEST class SampleSizeCalculator: diff --git a/spotify_confidence/analysis/frequentist/t_test.py b/spotify_confidence/analysis/frequentist/t_test.py index ee70261..37da0d2 100644 --- a/spotify_confidence/analysis/frequentist/t_test.py +++ b/spotify_confidence/analysis/frequentist/t_test.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Union, Iterable +from typing import Iterable, Union from pandas import DataFrame diff --git a/spotify_confidence/analysis/frequentist/z_test.py b/spotify_confidence/analysis/frequentist/z_test.py index b3c06d5..69ba8b2 100644 --- a/spotify_confidence/analysis/frequentist/z_test.py +++ b/spotify_confidence/analysis/frequentist/z_test.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Union, Iterable +from typing import Iterable, Union from pandas import DataFrame diff --git a/spotify_confidence/analysis/frequentist/z_test_linreg.py b/spotify_confidence/analysis/frequentist/z_test_linreg.py index 53dfab2..b4c8ab1 100644 --- a/spotify_confidence/analysis/frequentist/z_test_linreg.py +++ b/spotify_confidence/analysis/frequentist/z_test_linreg.py @@ -12,14 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Union, Iterable +from typing import Iterable, Union from pandas import DataFrame from spotify_confidence.analysis.constants import BONFERRONI, METHOD_COLUMN_NAME -from .experiment import Experiment + from ..abstract_base_classes.confidence_computer_abc import ConfidenceComputerABC from ..abstract_base_classes.confidence_grapher_abc import ConfidenceGrapherABC +from .experiment import Experiment class ZTestLinreg(Experiment): diff --git a/spotify_confidence/examples.py b/spotify_confidence/examples.py index 5b1a661..45a7a47 100644 --- a/spotify_confidence/examples.py +++ b/spotify_confidence/examples.py @@ -12,10 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -import pandas as pd -import numpy as np from itertools import product +import numpy as np +import pandas as pd + def example_data_binomial(): """ diff --git a/spotify_confidence/samplesize/sample_size_calculator.py b/spotify_confidence/samplesize/sample_size_calculator.py index 4c22506..fd5e727 100644 --- a/spotify_confidence/samplesize/sample_size_calculator.py +++ b/spotify_confidence/samplesize/sample_size_calculator.py @@ -12,12 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -from ipywidgets import widgets -from IPython.display import display -import scipy.stats as st -import numpy as np import math +import numpy as np +import scipy.stats as st +from IPython.display import display +from ipywidgets import widgets + class SampleSize(object): """Frequentist sample size calculations. @@ -285,7 +286,7 @@ def _calculate_sample_size_interactive(metric): layout=desc_layout, ) - baseline_title = widgets.HTML("Baseline " "proportion") + baseline_title = widgets.HTML("Baseline proportion") baseline_widget = widgets.FloatSlider(value=0.5, min=0.00001, max=0.99999, step=0.01, description="") baseline_desc = widgets.HTML( """ @@ -364,7 +365,7 @@ def reset_widget(b): widgets.HTML("

Target metric

"), widgets.VBox( children=[ - widgets.HTML("Minimal Detectable Effect " "size"), + widgets.HTML("Minimal Detectable Effect size"), mde_widget, mde_desc, ], @@ -501,7 +502,7 @@ def compare_against_optimal(current, optimal): else: treatment = "Variant " + str(i) - cell_str += f"
{treatment}: " f"{n_cell[i]:,} ({prop_cell[i] * 100:.1f}%)" + cell_str += f"
{treatment}: {n_cell[i]:,} ({prop_cell[i] * 100:.1f}%)" display(widgets.HTML(cell_str)) display(code_html) @@ -539,7 +540,7 @@ def _clean_treatments(treatments): equal to two. """ - error_string = "Treatments must be a whole number " "greater than or equal to two" + error_string = "Treatments must be a whole number greater than or equal to two" try: remainder = treatments % 1 except TypeError: @@ -569,7 +570,7 @@ def _clean_comparisons(comparisons): """ if comparisons not in ("control_vs_all", "all_vs_all"): - raise ValueError("comparisons must be either " '"control_vs_all" or "all_vs_all"') + raise ValueError('comparisons must be either "control_vs_all" or "all_vs_all"') else: return comparisons @@ -664,7 +665,7 @@ def _clean_treatment_costs(treatments, treatment_costs): or len(treatment_costs) != treatments ): raise TypeError( - "treatment_costs must be a list or numpy array of" "the same length as the number of treatments" + "treatment_costs must be a list or numpy array ofthe same length as the number of treatments" ) try: @@ -726,10 +727,10 @@ def _get_treatment_allocations(treatments, comparisons, treatment_costs, treatme ) elif not (treatment_allocations > 0).all(): - raise ValueError("treatment_allocations values " "must all be positive") + raise ValueError("treatment_allocations values must all be positive") elif not math.isclose(treatment_allocations.sum(), 1.0): - raise ValueError("treatment_allocations values " "must sum to one") + raise ValueError("treatment_allocations values must sum to one") else: return np.array(treatment_allocations) @@ -864,6 +865,6 @@ def _clean_binomial_mde(absolute_percentage_mde, baseline_proportion): baseline = SampleSize._validate_percentage(baseline_proportion) if baseline - mde < 0 and baseline + mde > 1: - raise ValueError("absolute_percentage_mde is too large " "given baseline_proportion") + raise ValueError("absolute_percentage_mde is too large given baseline_proportion") else: return mde diff --git a/tests/bayesian/test_betabinomial.py b/tests/bayesian/test_betabinomial.py index 3a6bf74..9f621b6 100644 --- a/tests/bayesian/test_betabinomial.py +++ b/tests/bayesian/test_betabinomial.py @@ -1,9 +1,10 @@ """Tests for `confidence` categorical variables.""" +import numpy as np +import pandas as pd import pytest + import spotify_confidence -import pandas as pd -import numpy as np spotify_confidence.options.set_option("randomization_seed", 1) diff --git a/tests/frequentist/test_bounds.py b/tests/frequentist/test_bounds.py index c85d7e9..d770ea3 100644 --- a/tests/frequentist/test_bounds.py +++ b/tests/frequentist/test_bounds.py @@ -1,11 +1,12 @@ -import pandas as pd import time + import numpy as np +import pandas as pd from pandas import Timestamp from spotify_confidence.analysis.frequentist.confidence_computers.z_test_computer import ( - sequential_bounds, compute_sequential_adjusted_alpha, + sequential_bounds, ) diff --git a/tests/frequentist/test_chisquared.py b/tests/frequentist/test_chisquared.py index 7b68aed..68c5320 100644 --- a/tests/frequentist/test_chisquared.py +++ b/tests/frequentist/test_chisquared.py @@ -7,7 +7,7 @@ import spotify_confidence from spotify_confidence.analysis.confidence_utils import power_calculation -from spotify_confidence.analysis.constants import POINT_ESTIMATE, VARIANCE, SFX1, SFX2, DENOMINATOR +from spotify_confidence.analysis.constants import DENOMINATOR, POINT_ESTIMATE, SFX1, SFX2, VARIANCE from spotify_confidence.analysis.frequentist.confidence_computers import chi_squared_computer as computer diff --git a/tests/frequentist/test_experiment.py b/tests/frequentist/test_experiment.py index 34946a4..1831094 100644 --- a/tests/frequentist/test_experiment.py +++ b/tests/frequentist/test_experiment.py @@ -3,7 +3,7 @@ import pytest import spotify_confidence -from spotify_confidence.analysis.constants import METHOD_COLUMN_NAME, ZTEST, ADJUSTED_LOWER, ADJUSTED_UPPER +from spotify_confidence.analysis.constants import ADJUSTED_LOWER, ADJUSTED_UPPER, METHOD_COLUMN_NAME, ZTEST class TestBootstrap(object): diff --git a/tests/frequentist/test_freqsamplesizecalculator.py b/tests/frequentist/test_freqsamplesizecalculator.py index 1eaf148..2964b8e 100644 --- a/tests/frequentist/test_freqsamplesizecalculator.py +++ b/tests/frequentist/test_freqsamplesizecalculator.py @@ -2,12 +2,12 @@ import pandas as pd from spotify_confidence.analysis.constants import ( - SPOT_1, - REQUIRED_SAMPLE_SIZE_METRIC, + ADJUSTED_LOWER, CI_WIDTH, - POWERED_EFFECT, POINT_ESTIMATE, - ADJUSTED_LOWER, + POWERED_EFFECT, + REQUIRED_SAMPLE_SIZE_METRIC, + SPOT_1, ) from spotify_confidence.analysis.frequentist.sample_size_calculator import SampleSizeCalculator from spotify_confidence.analysis.frequentist.z_test import ZTest diff --git a/tests/frequentist/test_ttest.py b/tests/frequentist/test_ttest.py index 61735c6..02a15fb 100644 --- a/tests/frequentist/test_ttest.py +++ b/tests/frequentist/test_ttest.py @@ -6,19 +6,19 @@ import spotify_confidence from spotify_confidence.analysis.constants import ( - POINT_ESTIMATE, - VARIANCE, + CI_LOWER, + CI_UPPER, + DECREASE_PREFFERED, + DENOMINATOR, + DIFFERENCE, + INCREASE_PREFFERED, NUMERATOR, NUMERATOR_SUM_OF_SQUARES, - DENOMINATOR, + P_VALUE, + POINT_ESTIMATE, SFX1, SFX2, - INCREASE_PREFFERED, - DECREASE_PREFFERED, - DIFFERENCE, - P_VALUE, - CI_UPPER, - CI_LOWER, + VARIANCE, ) from spotify_confidence.analysis.frequentist.confidence_computers import t_test_computer as computer diff --git a/tests/frequentist/test_ztest.py b/tests/frequentist/test_ztest.py index 957029b..8ca1b84 100644 --- a/tests/frequentist/test_ztest.py +++ b/tests/frequentist/test_ztest.py @@ -1,28 +1,28 @@ import numpy as np import pandas as pd import pytest +from statsmodels.stats.multitest import multipletests import spotify_confidence from spotify_confidence.analysis.constants import ( - INCREASE_PREFFERED, - DECREASE_PREFFERED, - POINT_ESTIMATE, - CI_LOWER, - CI_UPPER, - P_VALUE, ADJUSTED_LOWER, ADJUSTED_UPPER, - DIFFERENCE, BONFERRONI, - BONFERRONI_ONLY_COUNT_TWOSIDED, BONFERRONI_DO_NOT_COUNT_NON_INFERIORITY, + BONFERRONI_ONLY_COUNT_TWOSIDED, + CI_LOWER, + CI_UPPER, CORRECTION_METHODS, - SPOT_1, CORRECTION_METHODS_THAT_SUPPORT_CI, + DECREASE_PREFFERED, + DIFFERENCE, + INCREASE_PREFFERED, + P_VALUE, + POINT_ESTIMATE, POWERED_EFFECT, REQUIRED_SAMPLE_SIZE, + SPOT_1, ) -from statsmodels.stats.multitest import multipletests class TestPoweredEffectContinuousSingleMetric(object): diff --git a/tests/frequentist/test_ztest_linreg.py b/tests/frequentist/test_ztest_linreg.py index cb81a14..4989511 100644 --- a/tests/frequentist/test_ztest_linreg.py +++ b/tests/frequentist/test_ztest_linreg.py @@ -3,7 +3,7 @@ import pytest import spotify_confidence -from spotify_confidence.analysis.constants import REGRESSION_PARAM, DECREASE_PREFFERED, METHOD_COLUMN_NAME +from spotify_confidence.analysis.constants import DECREASE_PREFFERED, METHOD_COLUMN_NAME, REGRESSION_PARAM class TestUnivariateSingleMetric(object): diff --git a/tests/samplesize/test_samplesizecalculator.py b/tests/samplesize/test_samplesizecalculator.py index ccd2676..adb0ba0 100644 --- a/tests/samplesize/test_samplesizecalculator.py +++ b/tests/samplesize/test_samplesizecalculator.py @@ -1,8 +1,9 @@ """Tests for `confidence` sample size calculation.""" +import numpy as np import pytest + import spotify_confidence as conf -import numpy as np class TestSampleSizeCalc(object): diff --git a/tests/test_plot_precision.py b/tests/test_plot_precision.py index b5d507a..87d1d37 100644 --- a/tests/test_plot_precision.py +++ b/tests/test_plot_precision.py @@ -1,6 +1,7 @@ -import spotify_confidence import pandas as pd +import spotify_confidence + OUTPUT_DIR = "./tests/outputs/precision/" spotify_confidence.options.set_option("randomization_seed", 1) diff --git a/tox.ini b/tox.ini index 064fa09..cb42e5a 100644 --- a/tox.ini +++ b/tox.ini @@ -1,31 +1,18 @@ [tox] -envlist = py39, py310, py311, py312, py39-min +envlist = py39-min, py310, py311, py312 isolated_build = True requires = tox-uv -[gh-actions] -python = - 3.9: py39-min - 3.10: py310 - 3.11: py311 - 3.12: py312 - [testenv] -extras = dev +dependency_groups = dev commands = - black --check --diff spotify_confidence tests - flake8 spotify_confidence tests + ruff check + ruff format --check pytest -n auto --no-cov --basetemp={envtmpdir} {posargs} -[testenv:py312] -extras = dev -commands = - black --check --diff spotify_confidence tests - flake8 spotify_confidence tests - pytest -n auto --basetemp={envtmpdir} {posargs} - [testenv:py39-min] basepython = python3.9 +dependency_groups = dev deps = numpy==1.21.0 scipy==1.9.0 @@ -33,18 +20,7 @@ deps = statsmodels==0.13.5 chartify==5.0.0 ipywidgets==8.0.0 - black==23.7.0 - flake8==6.0.0 - pytest==7.0.0 - pytest-cov==4.0.0 - pytest-xdist==3.0.2 - coverage==7.0.0 commands = - black --check --diff spotify_confidence tests - flake8 spotify_confidence tests + ruff check + ruff format --check pytest -n auto --no-cov --basetemp={envtmpdir} {posargs} - -[flake8] -max-line-length = 120 -ignore = E203,E231,W503 -exclude = .venv,.tox,.git,dist,docs,*.egg,build,scratch.py,confidence_dev