dycw-utilities 0.109.13__tar.gz → 0.109.15__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.
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/PKG-INFO +1 -1
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/pyproject.toml +3 -2
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_iterables.py +1 -1
- dycw_utilities-0.109.15/src/tests/test_lightweight_charts.py +64 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_polars.py +1 -1
- dycw_utilities-0.109.15/src/tests/test_polars_ols.py +114 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/__init__.py +1 -1
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/altair.py +1 -2
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/iterables.py +5 -5
- dycw_utilities-0.109.15/src/utilities/lightweight_charts.py +96 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/polars.py +1 -1
- dycw_utilities-0.109.15/src/utilities/polars_ols.py +171 -0
- dycw_utilities-0.109.13/src/tests/test_polars_ols.py +0 -103
- dycw_utilities-0.109.13/src/utilities/polars_ols.py +0 -71
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/.gitignore +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/LICENSE +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/README.md +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/__init__.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/conftest.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/modules/__init__.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/modules/package_missing/__init__.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/modules/package_missing/module.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/modules/package_with/__init__.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/modules/package_with/outer_1.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/modules/package_with/outer_2.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/modules/package_without/__init__.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/modules/package_without/module_1.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/modules/package_without/module_2.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/modules/standalone.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/modules/with_imports.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/scripts/__init__.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/scripts/test_async_service/__init__.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/scripts/test_async_service/__main__.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/scripts/test_async_service/run.sh +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/scripts/test_queue_processor/__init__.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/scripts/test_queue_processor/__main__.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/scripts/test_queue_processor/run.sh +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_altair.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_astor.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_asyncio.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_atomicwrites.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_atools.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_cachetools.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_click.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_concurrent.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_contextlib.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_contextvars.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_cryptography.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_cvxpy.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_dataclasses.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_datetime.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_enum.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_errors.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_eventkit.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_fastapi.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_fpdf2.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_functions.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_functools.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_getpass.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_git.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_hashlib.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_http.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_hypothesis.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_ipython.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_jupyter.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_logging.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_loguru.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_luigi.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_math.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_memory_profiler.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_modules.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_more_itertools.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_numpy.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_operator.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_optuna.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_orjson.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_os.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_parse.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_pathlib.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_period.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_pickle.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_platform.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_pqdm.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_pydantic.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_pyinstrument.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_pyrsistent.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_pytest.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_pytest_regressions.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_python_dotenv.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_random.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_re.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_redis.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_reprlib.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_rich.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_scipy.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_sentinel.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_shelve.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_slack_sdk.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_socket.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_sqlalchemy.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_sqlalchemy_polars.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_streamlit.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_sys.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_tempfile.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_tenacity.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_text.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_threading.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_timer.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_traceback.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_traceback_funcs/__init__.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_traceback_funcs/chain.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_traceback_funcs/decorated_async.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_traceback_funcs/decorated_sync.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_traceback_funcs/error_bind.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_traceback_funcs/many.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_traceback_funcs/one.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_traceback_funcs/recursive.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_traceback_funcs/task_group_one.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_traceback_funcs/task_group_two.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_traceback_funcs/two.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_traceback_funcs/untraced.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_types.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_typing.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_typing_funcs/__init__.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_typing_funcs/no_future.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_typing_funcs/with_future.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_tzdata.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_tzlocal.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_uuid.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_version.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_warnings.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_whenever.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_zipfile.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/tests/test_zoneinfo.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/astor.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/asyncio.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/atomicwrites.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/atools.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/cachetools.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/click.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/concurrent.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/contextlib.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/contextvars.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/cryptography.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/cvxpy.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/dataclasses.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/datetime.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/enum.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/errors.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/eventkit.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/fastapi.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/fpdf2.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/functions.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/functools.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/getpass.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/git.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/hashlib.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/http.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/hypothesis.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/ipython.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/jupyter.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/logging.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/loguru.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/luigi.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/math.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/memory_profiler.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/modules.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/more_itertools.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/numpy.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/operator.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/optuna.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/orjson.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/os.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/parse.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/pathlib.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/period.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/pickle.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/platform.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/pqdm.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/py.typed +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/pydantic.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/pyinstrument.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/pyrsistent.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/pytest.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/pytest_regressions.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/python_dotenv.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/random.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/re.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/redis.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/reprlib.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/rich.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/scipy.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/sentinel.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/shelve.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/slack_sdk.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/socket.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/sqlalchemy.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/sqlalchemy_polars.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/streamlit.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/sys.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/tempfile.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/tenacity.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/text.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/threading.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/timer.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/traceback.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/types.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/typing.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/tzdata.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/tzlocal.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/uuid.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/version.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/warnings.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/whenever.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/zipfile.py +0 -0
- {dycw_utilities-0.109.13 → dycw_utilities-0.109.15}/src/utilities/zoneinfo.py +0 -0
@@ -29,6 +29,7 @@ dev = [
|
|
29
29
|
"httpx >= 0.28.1, < 0.29", # for fastapi
|
30
30
|
"hypothesis >= 6.131.7, < 6.132",
|
31
31
|
"img2pdf >= 0.6.0, < 0.7",
|
32
|
+
"lightweight-charts >= 2.1, < 2.2",
|
32
33
|
"loguru >= 0.7.3, < 0.8",
|
33
34
|
"luigi >= 3.6.0, < 3.7",
|
34
35
|
"memory-profiler >= 0.61.0, < 0.62",
|
@@ -91,7 +92,7 @@ dependencies = [
|
|
91
92
|
name = "dycw-utilities"
|
92
93
|
readme = "README.md"
|
93
94
|
requires-python = ">= 3.12"
|
94
|
-
version = "0.109.
|
95
|
+
version = "0.109.15"
|
95
96
|
|
96
97
|
[project.optional-dependencies]
|
97
98
|
test = [
|
@@ -334,7 +335,7 @@ zzz-test-zoneinfo = [
|
|
334
335
|
# bump-my-version
|
335
336
|
[tool.bumpversion]
|
336
337
|
allow_dirty = true
|
337
|
-
current_version = "0.109.
|
338
|
+
current_version = "0.109.15"
|
338
339
|
|
339
340
|
[[tool.bumpversion.files]]
|
340
341
|
filename = "src/utilities/__init__.py"
|
@@ -1058,7 +1058,7 @@ class TestOne:
|
|
1058
1058
|
|
1059
1059
|
@given(args=sampled_from([([],), ([], []), ([], [], [])]))
|
1060
1060
|
def test_error_empty(self, *, args: tuple[Iterable[Any], ...]) -> None:
|
1061
|
-
with raises(OneEmptyError, match=r"Iterable\(s\) must not be empty"):
|
1061
|
+
with raises(OneEmptyError, match=r"Iterable\(s\) .* must not be empty"):
|
1062
1062
|
_ = one(*args)
|
1063
1063
|
|
1064
1064
|
@given(iterable=sets(integers(), min_size=2))
|
@@ -0,0 +1,64 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import datetime as dt
|
4
|
+
from typing import cast
|
5
|
+
|
6
|
+
from lightweight_charts import Chart
|
7
|
+
from polars import DataFrame, Date, Float64, Int64, col
|
8
|
+
from pytest import fixture, raises
|
9
|
+
|
10
|
+
from utilities.lightweight_charts import (
|
11
|
+
_SetDataFrameEmptyError,
|
12
|
+
_SetDataFrameNonUniqueError,
|
13
|
+
set_dataframe,
|
14
|
+
)
|
15
|
+
|
16
|
+
|
17
|
+
@fixture
|
18
|
+
def df() -> DataFrame:
|
19
|
+
data = [
|
20
|
+
(dt.date(2000, 1, 1), 5824.0, 5824.75, 5823.75, 5824.25, 124),
|
21
|
+
(dt.date(2000, 1, 2), 5821.0, 5821.5, 5820.75, 5820.75, 146),
|
22
|
+
(dt.date(2000, 1, 3), 5820.0, 5821.0, 5819.75, 5820.5, 128),
|
23
|
+
(dt.date(2000, 1, 4), 5822.5, 5822.5, 5822.25, 5822.25, 78),
|
24
|
+
(dt.date(2000, 1, 5), 5822.5, 5822.5, 5821.5, 5821.75, 73),
|
25
|
+
(dt.date(2000, 1, 6), 5817.0, 5817.0, 5816.0, 5816.5, 301),
|
26
|
+
(dt.date(2000, 1, 7), 5817.75, 5818.75, 5817.5, 5818.75, 150),
|
27
|
+
(dt.date(2000, 1, 8), 5821.0, 5821.25, 5821.0, 5821.25, 75),
|
28
|
+
(dt.date(2000, 1, 9), 5818.0, 5819.0, 5818.0, 5818.75, 69),
|
29
|
+
(dt.date(2000, 1, 10), 5818.75, 5819.25, 5818.5, 5819.0, 67),
|
30
|
+
]
|
31
|
+
return DataFrame(
|
32
|
+
data=data,
|
33
|
+
schema={
|
34
|
+
"date": Date,
|
35
|
+
"open": Float64,
|
36
|
+
"high": Float64,
|
37
|
+
"low": Float64,
|
38
|
+
"close": Float64,
|
39
|
+
"volume": Int64,
|
40
|
+
},
|
41
|
+
orient="row",
|
42
|
+
)
|
43
|
+
|
44
|
+
|
45
|
+
class TestSetDataFrame:
|
46
|
+
def test_main(self, *, df: DataFrame) -> None:
|
47
|
+
chart = Chart()
|
48
|
+
set_dataframe(df, chart)
|
49
|
+
|
50
|
+
def test_error_empty(self, *, df: DataFrame) -> None:
|
51
|
+
df = df.drop("date")
|
52
|
+
with raises(
|
53
|
+
_SetDataFrameEmptyError,
|
54
|
+
match="At least 1 column must be of date/datetime type; got 0",
|
55
|
+
):
|
56
|
+
set_dataframe(df, cast("Chart", None))
|
57
|
+
|
58
|
+
def test_error_non_unique(self, *, df: DataFrame) -> None:
|
59
|
+
df = df.with_columns(col("date").alias("date2"))
|
60
|
+
with raises(
|
61
|
+
_SetDataFrameNonUniqueError,
|
62
|
+
match=r"Schema\(.*\) must contain exactly 1 date/datetime column; got 'date', 'date2' and perhaps more",
|
63
|
+
):
|
64
|
+
set_dataframe(df, cast("Chart", None))
|
@@ -755,7 +755,7 @@ class TestDataClassToDataFrame:
|
|
755
755
|
|
756
756
|
with raises(
|
757
757
|
_DataClassToDataFrameNonUniqueError,
|
758
|
-
match="Iterable .* must contain exactly
|
758
|
+
match="Iterable .* must contain exactly 1 class; got .*, .* and perhaps more",
|
759
759
|
):
|
760
760
|
_ = dataclass_to_dataframe([Example1(), Example2()])
|
761
761
|
|
@@ -0,0 +1,114 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from hypothesis import given
|
4
|
+
from hypothesis.strategies import sampled_from
|
5
|
+
from numpy import isclose
|
6
|
+
from polars import DataFrame, Float64, Series, Struct, col
|
7
|
+
from polars.testing import assert_frame_equal
|
8
|
+
from sklearn.linear_model import LinearRegression
|
9
|
+
|
10
|
+
from utilities.polars import concat_series, integers, normal
|
11
|
+
from utilities.polars_ols import compute_rolling_ols
|
12
|
+
|
13
|
+
|
14
|
+
class TestComputeRollingOLS:
|
15
|
+
def test_main_self(self) -> None:
|
16
|
+
df = self._df.with_columns(
|
17
|
+
compute_rolling_ols(
|
18
|
+
"y", "x1", "x2", window_size=5, min_periods=5, add_intercept=True
|
19
|
+
)
|
20
|
+
)
|
21
|
+
self._assert_series(df["ols"])
|
22
|
+
|
23
|
+
def test_main_series(self) -> None:
|
24
|
+
ols = compute_rolling_ols(
|
25
|
+
self._df["y"],
|
26
|
+
self._df["x1"],
|
27
|
+
self._df["x2"],
|
28
|
+
window_size=5,
|
29
|
+
min_periods=5,
|
30
|
+
add_intercept=True,
|
31
|
+
)
|
32
|
+
self._assert_series(ols)
|
33
|
+
|
34
|
+
@given(
|
35
|
+
case=sampled_from([
|
36
|
+
(
|
37
|
+
slice(-7, -2),
|
38
|
+
[0.3619563208480195, 0.6583229512154678],
|
39
|
+
-1.386023798329262,
|
40
|
+
7.434533329394103,
|
41
|
+
0.994253238813284,
|
42
|
+
),
|
43
|
+
(
|
44
|
+
slice(-6, -1),
|
45
|
+
[0.35564162435283264, 0.6656931556738643],
|
46
|
+
-0.5626805730005437,
|
47
|
+
-51.903154626050124,
|
48
|
+
0.9979752966843768,
|
49
|
+
),
|
50
|
+
(
|
51
|
+
slice(-5, None),
|
52
|
+
[0.3100421300754358, 0.6753578168818635],
|
53
|
+
0.48493124625502837,
|
54
|
+
-36.70039604095908,
|
55
|
+
0.9977272526713715,
|
56
|
+
),
|
57
|
+
])
|
58
|
+
)
|
59
|
+
def test_tail(
|
60
|
+
self, *, case: tuple[slice, list[float], float, float, float]
|
61
|
+
) -> None:
|
62
|
+
slice_, coeffs, intercept, prediction, r2 = case
|
63
|
+
df = self._df[slice_]
|
64
|
+
X = df.select("x1", "x2").to_numpy() # noqa: N806
|
65
|
+
y = df.select("y").to_numpy()
|
66
|
+
model = LinearRegression()
|
67
|
+
model = model.fit(X, y)
|
68
|
+
assert isclose(model.coef_, coeffs).all()
|
69
|
+
assert isclose(model.intercept_, intercept)
|
70
|
+
assert isclose(model.predict(X)[-1], prediction).all()
|
71
|
+
assert isclose(model.score(X, y), r2)
|
72
|
+
|
73
|
+
@property
|
74
|
+
def _df(self) -> DataFrame:
|
75
|
+
n = 20
|
76
|
+
return concat_series(
|
77
|
+
integers(n, -100, high=100, seed=0).alias("x1"),
|
78
|
+
integers(n, -100, high=100, seed=1).alias("x2"),
|
79
|
+
).with_columns(
|
80
|
+
((col("x1") + 2 * col("x2") + normal(n, scale=10.0, seed=2)) / 3).alias("y")
|
81
|
+
)
|
82
|
+
|
83
|
+
def _assert_series(self, series: Series, /) -> None:
|
84
|
+
df = series.struct.unnest()
|
85
|
+
tail = df[-10:]
|
86
|
+
# fmt: off
|
87
|
+
data = [
|
88
|
+
({"x1": 0.333396198442681, "x2": 0.6845517746145712, "const": 0.2808021232120448}, 59.921571424913495, 1.67032007883995, 0.9955364659986504),
|
89
|
+
({"x1": 0.322785525889542, "x2": 0.6896341527044252, "const": 0.5401793852579858}, 15.974446064929626, -0.3429678871268038, 0.9961762567103958),
|
90
|
+
({"x1": 0.31042868991153927, "x2": 0.7055685710743383, "const": 1.145326562525439}, -31.310827706894123, -0.45191863996575066, 0.998022262986332),
|
91
|
+
({"x1": 0.33311466967931097, "x2": 0.684137842579758, "const": -0.7961518480794516}, 50.66821598287034, -2.975371834066671, 0.9974533939791341),
|
92
|
+
({"x1": 0.35299385150914864, "x2": 0.6758890569593843, "const": -0.9377907849336107}, -0.8749325340834626, 1.0581261048863142, 0.9973453833170313),
|
93
|
+
({"x1": 0.351300641938209, "x2": 0.6456834722890913, "const": -1.859577387752822}, 1.6809655259738476, 0.3217076349681922, 0.9951571413022856),
|
94
|
+
({"x1": 0.3583378199895871, "x2": 0.6588347796692774, "const": -1.109675446287481}, 26.65448170418155, 2.496480675700724, 0.9933751737130443),
|
95
|
+
({"x1": 0.36195632084801765, "x2": 0.658322951215466, "const": -1.3860237983291754}, 7.43453332939416, -0.791818995629618, 0.9905085882663488),
|
96
|
+
({"x1": 0.35564162435283225, "x2": 0.6656931556738634, "const": -0.562680573000551}, -51.90315462605006, 0.6592474497562932, 0.9973576833556038),
|
97
|
+
({"x1": 0.3100421300754357, "x2": 0.675357816881863, "const": 0.48493124625501927}, -36.70039604095908, -0.6071841038068868, 0.9978541580643828),
|
98
|
+
]
|
99
|
+
# fmt: on
|
100
|
+
expected = DataFrame(
|
101
|
+
data=data,
|
102
|
+
schema={
|
103
|
+
"coefficients": Struct({
|
104
|
+
"x1": Float64,
|
105
|
+
"x2": Float64,
|
106
|
+
"const": Float64,
|
107
|
+
}),
|
108
|
+
"predictions": Float64,
|
109
|
+
"residuals": Float64,
|
110
|
+
"R2": Float64,
|
111
|
+
},
|
112
|
+
orient="row",
|
113
|
+
)
|
114
|
+
assert_frame_equal(tail, expected)
|
@@ -22,7 +22,6 @@ from altair import (
|
|
22
22
|
vconcat,
|
23
23
|
)
|
24
24
|
from altair.utils.schemapi import Undefined
|
25
|
-
from polars import Date, Datetime
|
26
25
|
|
27
26
|
from utilities.functions import ensure_bytes, ensure_number
|
28
27
|
from utilities.iterables import always_iterable
|
@@ -66,7 +65,7 @@ def plot_dataframes(
|
|
66
65
|
) -> VConcatChart:
|
67
66
|
"""Plot a DataFrame as a set of time series, with a multi-line tooltip."""
|
68
67
|
import polars as pl
|
69
|
-
from polars import int_range
|
68
|
+
from polars import Date, Datetime, int_range
|
70
69
|
|
71
70
|
from utilities.polars import replace_time_zone
|
72
71
|
|
@@ -1009,7 +1009,7 @@ def one(*iterables: Iterable[_T]) -> _T:
|
|
1009
1009
|
try:
|
1010
1010
|
first = next(it)
|
1011
1011
|
except StopIteration:
|
1012
|
-
raise OneEmptyError from None
|
1012
|
+
raise OneEmptyError(iterables=iterables) from None
|
1013
1013
|
try:
|
1014
1014
|
second = next(it)
|
1015
1015
|
except StopIteration:
|
@@ -1018,19 +1018,19 @@ def one(*iterables: Iterable[_T]) -> _T:
|
|
1018
1018
|
|
1019
1019
|
|
1020
1020
|
@dataclass(kw_only=True, slots=True)
|
1021
|
-
class OneError(Exception):
|
1021
|
+
class OneError(Exception, Generic[_T]):
|
1022
|
+
iterables: tuple[Iterable[_T], ...]
|
1022
1023
|
|
1023
1024
|
|
1024
1025
|
@dataclass(kw_only=True, slots=True)
|
1025
|
-
class OneEmptyError(OneError):
|
1026
|
+
class OneEmptyError(OneError[_T]):
|
1026
1027
|
@override
|
1027
1028
|
def __str__(self) -> str:
|
1028
|
-
return "Iterable(s) must not be empty"
|
1029
|
+
return f"Iterable(s) {get_repr(self.iterables)} must not be empty"
|
1029
1030
|
|
1030
1031
|
|
1031
1032
|
@dataclass(kw_only=True, slots=True)
|
1032
1033
|
class OneNonUniqueError(OneError, Generic[_T]):
|
1033
|
-
iterables: tuple[Iterable[_T], ...]
|
1034
1034
|
first: _T
|
1035
1035
|
second: _T
|
1036
1036
|
|
@@ -0,0 +1,96 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from contextlib import asynccontextmanager
|
4
|
+
from dataclasses import dataclass
|
5
|
+
from typing import TYPE_CHECKING, override
|
6
|
+
|
7
|
+
from utilities.iterables import OneEmptyError, OneNonUniqueError, one
|
8
|
+
from utilities.reprlib import get_repr
|
9
|
+
|
10
|
+
if TYPE_CHECKING:
|
11
|
+
from collections.abc import AsyncIterator
|
12
|
+
|
13
|
+
from lightweight_charts import AbstractChart, Chart
|
14
|
+
from lightweight_charts.abstract import SeriesCommon
|
15
|
+
from polars import DataFrame
|
16
|
+
from polars._typing import SchemaDict
|
17
|
+
|
18
|
+
from utilities.types import PathLike
|
19
|
+
|
20
|
+
|
21
|
+
##
|
22
|
+
|
23
|
+
|
24
|
+
def save_chart(chart: Chart, path: PathLike, /, *, overwrite: bool = False) -> None:
|
25
|
+
"""Atomically save a chart to disk."""
|
26
|
+
from utilities.atomicwrites import writer # pragma: no cover
|
27
|
+
|
28
|
+
chart.show(block=False) # pragma: no cover
|
29
|
+
with ( # pragma: no cover
|
30
|
+
writer(path, overwrite=overwrite) as temp,
|
31
|
+
temp.open(mode="wb") as fh,
|
32
|
+
):
|
33
|
+
_ = fh.write(chart.screenshot())
|
34
|
+
chart.exit() # pragma: no cover
|
35
|
+
|
36
|
+
|
37
|
+
##
|
38
|
+
|
39
|
+
|
40
|
+
def set_dataframe(df: DataFrame, obj: AbstractChart | SeriesCommon, /) -> None:
|
41
|
+
"""Set a `polars` DataFrame onto a Chart."""
|
42
|
+
from polars import Date, Datetime, col # pragma: no cover
|
43
|
+
|
44
|
+
try:
|
45
|
+
name = one(k for k, v in df.schema.items() if isinstance(v, Date | Datetime))
|
46
|
+
except OneEmptyError:
|
47
|
+
raise _SetDataFrameEmptyError(schema=df.schema) from None
|
48
|
+
except OneNonUniqueError as error:
|
49
|
+
raise _SetDataFrameNonUniqueError(
|
50
|
+
schema=df.schema, first=error.first, second=error.second
|
51
|
+
) from None
|
52
|
+
return obj.set(
|
53
|
+
df.select(
|
54
|
+
col(name).alias("date").dt.strftime("iso"),
|
55
|
+
*[c for c in df.columns if c != name],
|
56
|
+
).to_pandas()
|
57
|
+
)
|
58
|
+
|
59
|
+
|
60
|
+
@dataclass(kw_only=True, slots=True)
|
61
|
+
class SetDataFrameError(Exception):
|
62
|
+
schema: SchemaDict
|
63
|
+
|
64
|
+
|
65
|
+
@dataclass(kw_only=True, slots=True)
|
66
|
+
class _SetDataFrameEmptyError(SetDataFrameError):
|
67
|
+
@override
|
68
|
+
def __str__(self) -> str:
|
69
|
+
return "At least 1 column must be of date/datetime type; got 0"
|
70
|
+
|
71
|
+
|
72
|
+
@dataclass(kw_only=True, slots=True)
|
73
|
+
class _SetDataFrameNonUniqueError(SetDataFrameError):
|
74
|
+
first: str
|
75
|
+
second: str
|
76
|
+
|
77
|
+
@override
|
78
|
+
def __str__(self) -> str:
|
79
|
+
return f"{get_repr(self.schema)} must contain exactly 1 date/datetime column; got {self.first!r}, {self.second!r} and perhaps more"
|
80
|
+
|
81
|
+
|
82
|
+
##
|
83
|
+
|
84
|
+
|
85
|
+
@asynccontextmanager
|
86
|
+
async def yield_chart(chart: Chart, /) -> AsyncIterator[None]:
|
87
|
+
"""Yield a chart for visualization in a notebook."""
|
88
|
+
try: # pragma: no cover
|
89
|
+
yield await chart.show_async()
|
90
|
+
except BaseException: # pragma: no cover # noqa: BLE001, S110
|
91
|
+
pass
|
92
|
+
finally: # pragma: no cover
|
93
|
+
chart.exit()
|
94
|
+
|
95
|
+
|
96
|
+
__all__ = ["save_chart", "set_dataframe", "yield_chart"]
|
@@ -714,7 +714,7 @@ class _DataClassToDataFrameNonUniqueError(DataClassToDataFrameError):
|
|
714
714
|
|
715
715
|
@override
|
716
716
|
def __str__(self) -> str:
|
717
|
-
return f"Iterable {get_repr(self.objs)} must contain exactly
|
717
|
+
return f"Iterable {get_repr(self.objs)} must contain exactly 1 class; got {self.first}, {self.second} and perhaps more"
|
718
718
|
|
719
719
|
|
720
720
|
##
|
@@ -0,0 +1,171 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import TYPE_CHECKING, overload
|
4
|
+
|
5
|
+
from polars import Expr, Series, struct
|
6
|
+
from polars_ols import RollingKwargs, compute_rolling_least_squares
|
7
|
+
|
8
|
+
from utilities.errors import ImpossibleCaseError
|
9
|
+
from utilities.functions import is_sequence_of
|
10
|
+
from utilities.polars import concat_series, ensure_expr_or_series
|
11
|
+
|
12
|
+
if TYPE_CHECKING:
|
13
|
+
from polars._typing import IntoExprColumn
|
14
|
+
from polars_ols import NullPolicy
|
15
|
+
|
16
|
+
from utilities.polars import ExprLike
|
17
|
+
|
18
|
+
|
19
|
+
@overload
|
20
|
+
def compute_rolling_ols(
|
21
|
+
target: ExprLike,
|
22
|
+
*features: ExprLike,
|
23
|
+
sample_weights: ExprLike | None = None,
|
24
|
+
add_intercept: bool = False,
|
25
|
+
null_policy: NullPolicy = "drop_window",
|
26
|
+
window_size: int = 1000000,
|
27
|
+
min_periods: int | None = None,
|
28
|
+
use_woodbury: bool | None = None,
|
29
|
+
alpha: float | None = None,
|
30
|
+
) -> Expr: ...
|
31
|
+
@overload
|
32
|
+
def compute_rolling_ols(
|
33
|
+
target: Series,
|
34
|
+
*features: Series,
|
35
|
+
sample_weights: Series | None = None,
|
36
|
+
add_intercept: bool = False,
|
37
|
+
null_policy: NullPolicy = "drop_window",
|
38
|
+
window_size: int = 1000000,
|
39
|
+
min_periods: int | None = None,
|
40
|
+
use_woodbury: bool | None = None,
|
41
|
+
alpha: float | None = None,
|
42
|
+
) -> Series: ...
|
43
|
+
@overload
|
44
|
+
def compute_rolling_ols(
|
45
|
+
target: IntoExprColumn,
|
46
|
+
*features: IntoExprColumn,
|
47
|
+
sample_weights: IntoExprColumn | None = None,
|
48
|
+
add_intercept: bool = False,
|
49
|
+
null_policy: NullPolicy = "drop_window",
|
50
|
+
window_size: int = 1000000,
|
51
|
+
min_periods: int | None = None,
|
52
|
+
use_woodbury: bool | None = None,
|
53
|
+
alpha: float | None = None,
|
54
|
+
) -> Expr | Series: ...
|
55
|
+
def compute_rolling_ols(
|
56
|
+
target: IntoExprColumn,
|
57
|
+
*features: IntoExprColumn,
|
58
|
+
sample_weights: IntoExprColumn | None = None,
|
59
|
+
add_intercept: bool = False,
|
60
|
+
null_policy: NullPolicy = "drop_window",
|
61
|
+
window_size: int = 1000000,
|
62
|
+
min_periods: int | None = None,
|
63
|
+
use_woodbury: bool | None = None,
|
64
|
+
alpha: float | None = None,
|
65
|
+
) -> Expr | Series:
|
66
|
+
"""Compute a rolling OLS."""
|
67
|
+
target = ensure_expr_or_series(target)
|
68
|
+
features2 = tuple(map(ensure_expr_or_series, features))
|
69
|
+
sample_weights = (
|
70
|
+
None if sample_weights is None else ensure_expr_or_series(sample_weights)
|
71
|
+
)
|
72
|
+
if (
|
73
|
+
isinstance(target, Expr)
|
74
|
+
and is_sequence_of(features2, Expr)
|
75
|
+
and ((sample_weights is None) or isinstance(sample_weights, Expr))
|
76
|
+
):
|
77
|
+
return _compute_rolling_ols_expr(
|
78
|
+
target,
|
79
|
+
*features2,
|
80
|
+
sample_weights=sample_weights,
|
81
|
+
add_intercept=add_intercept,
|
82
|
+
null_policy=null_policy,
|
83
|
+
window_size=window_size,
|
84
|
+
min_periods=min_periods,
|
85
|
+
use_woodbury=use_woodbury,
|
86
|
+
alpha=alpha,
|
87
|
+
)
|
88
|
+
if (
|
89
|
+
isinstance(target, Series)
|
90
|
+
and is_sequence_of(features2, Series)
|
91
|
+
and ((sample_weights is None) or isinstance(sample_weights, Series))
|
92
|
+
):
|
93
|
+
return concat_series(
|
94
|
+
target, *features2, *([] if sample_weights is None else [sample_weights])
|
95
|
+
).with_columns(
|
96
|
+
_compute_rolling_ols_expr(
|
97
|
+
target.name,
|
98
|
+
*(f.name for f in features2),
|
99
|
+
sample_weights=None if sample_weights is None else sample_weights.name,
|
100
|
+
add_intercept=add_intercept,
|
101
|
+
null_policy=null_policy,
|
102
|
+
window_size=window_size,
|
103
|
+
min_periods=min_periods,
|
104
|
+
use_woodbury=use_woodbury,
|
105
|
+
alpha=alpha,
|
106
|
+
)
|
107
|
+
)["ols"]
|
108
|
+
raise ImpossibleCaseError( # pragma: no cover
|
109
|
+
case=[f"{target=}", f"{features2=}", f"{sample_weights=}"]
|
110
|
+
)
|
111
|
+
|
112
|
+
|
113
|
+
def _compute_rolling_ols_expr(
|
114
|
+
target: ExprLike,
|
115
|
+
*features: ExprLike,
|
116
|
+
sample_weights: ExprLike | None = None,
|
117
|
+
add_intercept: bool = False,
|
118
|
+
null_policy: NullPolicy = "drop_window",
|
119
|
+
window_size: int = 1000000,
|
120
|
+
min_periods: int | None = None,
|
121
|
+
use_woodbury: bool | None = None,
|
122
|
+
alpha: float | None = None,
|
123
|
+
) -> Expr:
|
124
|
+
"""Compute a rolling OLS."""
|
125
|
+
target = ensure_expr_or_series(target)
|
126
|
+
features2 = tuple(map(ensure_expr_or_series, features))
|
127
|
+
sample_weights = (
|
128
|
+
None if sample_weights is None else ensure_expr_or_series(sample_weights)
|
129
|
+
)
|
130
|
+
rolling_kwargs = RollingKwargs(
|
131
|
+
null_policy=null_policy,
|
132
|
+
window_size=window_size,
|
133
|
+
min_periods=min_periods,
|
134
|
+
use_woodbury=use_woodbury,
|
135
|
+
alpha=alpha,
|
136
|
+
)
|
137
|
+
coefficients = compute_rolling_least_squares(
|
138
|
+
target,
|
139
|
+
*features2,
|
140
|
+
sample_weights=sample_weights,
|
141
|
+
add_intercept=add_intercept,
|
142
|
+
mode="coefficients",
|
143
|
+
rolling_kwargs=rolling_kwargs,
|
144
|
+
).alias("coefficients")
|
145
|
+
predictions = compute_rolling_least_squares(
|
146
|
+
target,
|
147
|
+
*features2,
|
148
|
+
sample_weights=sample_weights,
|
149
|
+
add_intercept=add_intercept,
|
150
|
+
mode="predictions",
|
151
|
+
rolling_kwargs=rolling_kwargs,
|
152
|
+
).alias("predictions")
|
153
|
+
residuals = compute_rolling_least_squares(
|
154
|
+
target,
|
155
|
+
*features2,
|
156
|
+
sample_weights=sample_weights,
|
157
|
+
add_intercept=add_intercept,
|
158
|
+
mode="residuals",
|
159
|
+
rolling_kwargs=rolling_kwargs,
|
160
|
+
).alias("residuals")
|
161
|
+
ssr = (residuals**2).rolling_sum(window_size, min_samples=min_periods).alias("SSR")
|
162
|
+
sst = (
|
163
|
+
((target - target.rolling_mean(window_size, min_samples=min_periods)) ** 2)
|
164
|
+
.rolling_sum(window_size, min_samples=min_periods)
|
165
|
+
.alias("SST")
|
166
|
+
)
|
167
|
+
r2 = (1 - ssr / sst).alias("R2")
|
168
|
+
return struct(coefficients, predictions, residuals, r2).alias("ols")
|
169
|
+
|
170
|
+
|
171
|
+
__all__ = ["compute_rolling_ols"]
|
@@ -1,103 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
from hypothesis import given
|
4
|
-
from hypothesis.strategies import sampled_from
|
5
|
-
from numpy import isclose
|
6
|
-
from polars import DataFrame, Float64, Struct, UInt32, col
|
7
|
-
from polars.testing import assert_frame_equal
|
8
|
-
from sklearn.linear_model import LinearRegression
|
9
|
-
|
10
|
-
from utilities.polars import concat_series, integers, normal
|
11
|
-
from utilities.polars_ols import compute_rolling_ols
|
12
|
-
|
13
|
-
|
14
|
-
class TestComputeRollingOLS:
|
15
|
-
def test_ols_results(self) -> None:
|
16
|
-
result = self._df["ols"].struct.unnest().with_row_index()
|
17
|
-
# fmt: off
|
18
|
-
exp_values = [
|
19
|
-
(10, {"x1": 0.333396198442681, "x2": 0.6845517746145712, "const": 0.2808021232120448}, 59.921571424913495, 1.67032007883995, 0.9955364659986504),
|
20
|
-
(11, {"x1": 0.322785525889542, "x2": 0.6896341527044252, "const": 0.5401793852579858}, 15.974446064929626, -0.3429678871268038, 0.9961762567103958),
|
21
|
-
(12, {"x1": 0.31042868991153927, "x2": 0.7055685710743383, "const": 1.145326562525439}, -31.310827706894123, -0.45191863996575066, 0.998022262986332),
|
22
|
-
(13, {"x1": 0.33311466967931097, "x2": 0.684137842579758, "const": -0.7961518480794516}, 50.66821598287034, -2.975371834066671, 0.9974533939791341),
|
23
|
-
(14, {"x1": 0.35299385150914864, "x2": 0.6758890569593843, "const": -0.9377907849336107}, -0.8749325340834626, 1.0581261048863142, 0.9973453833170313),
|
24
|
-
(15, {"x1": 0.351300641938209, "x2": 0.6456834722890913, "const": -1.859577387752822}, 1.6809655259738476, 0.3217076349681922, 0.9951571413022856),
|
25
|
-
(16, {"x1": 0.3583378199895871, "x2": 0.6588347796692774, "const": -1.109675446287481}, 26.65448170418155, 2.496480675700724, 0.9933751737130443),
|
26
|
-
(17, {"x1": 0.36195632084801765, "x2": 0.658322951215466, "const": -1.3860237983291754}, 7.43453332939416, -0.791818995629618, 0.9905085882663488),
|
27
|
-
(18, {"x1": 0.35564162435283225, "x2": 0.6656931556738634, "const": -0.562680573000551}, -51.90315462605006, 0.6592474497562932, 0.9973576833556038),
|
28
|
-
(19, {"x1": 0.3100421300754357, "x2": 0.675357816881863, "const": 0.48493124625501927}, -36.70039604095908, -0.6071841038068868, 0.9978541580643828),
|
29
|
-
]
|
30
|
-
# fmt: on
|
31
|
-
expected = DataFrame(
|
32
|
-
data=exp_values,
|
33
|
-
schema={
|
34
|
-
"index": UInt32,
|
35
|
-
"coefficients": Struct({
|
36
|
-
"x1": Float64,
|
37
|
-
"x2": Float64,
|
38
|
-
"const": Float64,
|
39
|
-
}),
|
40
|
-
"predictions": Float64,
|
41
|
-
"residuals": Float64,
|
42
|
-
"R2": Float64,
|
43
|
-
},
|
44
|
-
orient="row",
|
45
|
-
)
|
46
|
-
assert_frame_equal(result[-10:], expected)
|
47
|
-
|
48
|
-
@given(
|
49
|
-
case=sampled_from([
|
50
|
-
(
|
51
|
-
slice(-7, -2),
|
52
|
-
[0.3619563208480195, 0.6583229512154678],
|
53
|
-
-1.386023798329262,
|
54
|
-
7.434533329394103,
|
55
|
-
0.994253238813284,
|
56
|
-
),
|
57
|
-
(
|
58
|
-
slice(-6, -1),
|
59
|
-
[0.35564162435283264, 0.6656931556738643],
|
60
|
-
-0.5626805730005437,
|
61
|
-
-51.903154626050124,
|
62
|
-
0.9979752966843768,
|
63
|
-
),
|
64
|
-
(
|
65
|
-
slice(-5, None),
|
66
|
-
[0.3100421300754358, 0.6753578168818635],
|
67
|
-
0.48493124625502837,
|
68
|
-
-36.70039604095908,
|
69
|
-
0.9977272526713715,
|
70
|
-
),
|
71
|
-
])
|
72
|
-
)
|
73
|
-
def test_tail(
|
74
|
-
self, *, case: tuple[slice, list[float], float, float, float]
|
75
|
-
) -> None:
|
76
|
-
slice_, coeffs, intercept, prediction, r2 = case
|
77
|
-
df = self._df[slice_]
|
78
|
-
X = df.select("x1", "x2").to_numpy() # noqa: N806
|
79
|
-
y = df.select("y").to_numpy()
|
80
|
-
model = LinearRegression()
|
81
|
-
model = model.fit(X, y)
|
82
|
-
assert isclose(model.coef_, coeffs).all()
|
83
|
-
assert isclose(model.intercept_, intercept)
|
84
|
-
assert isclose(model.predict(X)[-1], prediction).all()
|
85
|
-
assert isclose(model.score(X, y), r2)
|
86
|
-
|
87
|
-
@property
|
88
|
-
def _df(self) -> DataFrame:
|
89
|
-
n = 20
|
90
|
-
return (
|
91
|
-
concat_series(
|
92
|
-
integers(n, -100, high=100, seed=0).alias("x1"),
|
93
|
-
integers(n, -100, high=100, seed=1).alias("x2"),
|
94
|
-
)
|
95
|
-
.with_columns(
|
96
|
-
y=(col("x1") + 2 * col("x2") + normal(n, scale=10.0, seed=2)) / 3
|
97
|
-
)
|
98
|
-
.with_columns(
|
99
|
-
compute_rolling_ols(
|
100
|
-
"y", "x1", "x2", window_size=5, min_periods=5, add_intercept=True
|
101
|
-
)
|
102
|
-
)
|
103
|
-
)
|