Skip to content

Commit

Permalink
Add release pipeline (#11)
Browse files Browse the repository at this point in the history
* Add release pipeline

* Improve `pyproject.toml`

* Remove url property
  • Loading branch information
jessegrabowski committed Aug 30, 2024
1 parent 251adbd commit 4ca0ea7
Show file tree
Hide file tree
Showing 14 changed files with 894 additions and 128 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
tsdisagg/_version.py export-subst
47 changes: 47 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: release-pipeline

on:
release:
types:
- created

jobs:
release-job:
runs-on: ubuntu-latest
env:
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: 3.11
- name: Install release tooling
run: |
pip install twine wheel numpy setuptools versioneer
- name: Build package
run: |
python setup.py sdist bdist_wheel
- name: Check version number match
run: |
echo "GITHUB_REF: ${GITHUB_REF}"
# The GITHUB_REF should be something like "refs/tags/v1.2.3"
# Make sure the package version is the same as the tag
grep -Rq "^Version: ${GITHUB_REF:11}$" tsdisagg.egg-info/PKG-INFO
- name: Publish to PyPI
run: |
twine check dist/*
twine upload --repository pypi --username __token__ --password ${PYPI_TOKEN} dist/*
test-install-job:
needs: release-job
runs-on: ubuntu-latest
steps:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: 3.7
- name: Give PyPI a chance to update the index
run: sleep 240
- name: Install from PyPI
run: |
pip install tsdisagg==${GITHUB_REF:11}
12 changes: 3 additions & 9 deletions Examples.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -82,21 +82,15 @@
"sales_a.columns = [\"sales\"]\n",
"\n",
"exports_q = pd.read_csv(\"tests/data/exports_q.csv\", index_col=0)\n",
"exports_q.index = pd.date_range(\n",
" start=\"1972-01-01\", freq=\"QS\", periods=exports_q.shape[0]\n",
")\n",
"exports_q.index = pd.date_range(start=\"1972-01-01\", freq=\"QS\", periods=exports_q.shape[0])\n",
"exports_q.columns = [\"exports\"]\n",
"\n",
"exports_m = pd.read_csv(\"tests/data/exports_m.csv\", index_col=0)\n",
"exports_m.index = pd.date_range(\n",
" start=\"1972-01-01\", freq=\"MS\", periods=exports_m.shape[0]\n",
")\n",
"exports_m.index = pd.date_range(start=\"1972-01-01\", freq=\"MS\", periods=exports_m.shape[0])\n",
"exports_m.columns = [\"exports\"]\n",
"\n",
"imports_q = pd.read_csv(\"tests/data/imports_q.csv\", index_col=0)\n",
"imports_q.index = pd.date_range(\n",
" start=\"1972-01-01\", freq=\"QS-OCT\", periods=imports_q.shape[0]\n",
")\n",
"imports_q.index = pd.date_range(start=\"1972-01-01\", freq=\"QS-OCT\", periods=imports_q.shape[0])\n",
"imports_q.columns = [\"imports\"]"
]
},
Expand Down
1 change: 1 addition & 0 deletions conda_envs/tsdisagg_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ dependencies:
- pytest
- pytest-cov
- hypothesis
- versioneer
70 changes: 70 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,67 @@
[project]
name = "tsdisagg"
dynamic = ['version']
description = "Statistical tools for converting low frequency time series data to higher frequencies"
license = { file = 'LICENSE'}
authors = [{name = "Jesse Grabowski", email="[email protected]"}]
classifiers = [
"License :: OSI Approved :: MIT License",
"Development Status :: 4 - Beta",
"Intended Audience :: Science/Research",
"Programming Language :: Python",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Scientific/Engineering",
"Topic :: Scientific/Engineering :: Mathematics"
]

keywords = [
"time series",
"decomposition",
"datetime",
"econometrics"
]

dependencies = [
"numpy",
"pandas >= 2.2.0",
"scipy"
]

[project.urls]
Repository = "https://github.com/jessegrabowski/tsdisagg.git"
Issues = "https://github.com/jessegrabowski/tsdisagg/issues"


[project.optional-dependencies]
dev = [
"pre-commit",
"pytest",
"pytest-cov",
"versioneer"
]


[build-system]
requires = [
"numpy",
"setuptools",
"versioneer[toml]",
]
build-backend = "setuptools.build_meta"


[tool.versioneer]
VCS = "git"
style = "pep440"
versionfile_source = "tsdisagg/_version.py"
versionfile_build = "tsdisagg/_version.py"
tag_prefix = 'v'

[tool.setuptools]
py-modules = ['tsdisagg']

[tool.pytest.ini_options]
minversion = "6.0"
xfail_strict = true
Expand All @@ -13,6 +77,11 @@ exclude_lines = [
"if TYPE_CHECKING:",
]

[tool.ruff]
line-length = 100
target-version = "py310"
extend-exclude = ["tsdisagg/_version.py", "setup.py"]


[tool.ruff.lint]
select = ["D", "E", "F", "I", "UP", "W", "RUF"]
Expand Down Expand Up @@ -46,5 +115,6 @@ ignore = [
"D417",
]


[tool.ruff.lint.isort]
lines-between-types = 1
23 changes: 0 additions & 23 deletions setup.cfg

This file was deleted.

20 changes: 18 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
from setuptools import setup
from setuptools import find_packages, setup
from setuptools.dist import Distribution

setup()
import versioneer

dist = Distribution()
dist.parse_config_files()


NAME: str = dist.get_name() # type: ignore


if __name__ == "__main__":
setup(
name=NAME,
version=versioneer.get_version(),
cmdclass=versioneer.get_cmdclass(),
packages=find_packages(exclude=["tests*"]),
)
42 changes: 11 additions & 31 deletions tests/test_disaggregation.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import unittest

from typing import Callable
from collections.abc import Callable

import numpy as np
import pandas as pd
Expand Down Expand Up @@ -46,9 +46,7 @@ def generate_random_index_pair(
A tuple containing the low-frequency and high-frequency DatetimeIndexes.
"""
if start is None:
start = pd.Timestamp.now().normalize() - pd.DateOffset(
years=np.random.randint(0, 30)
)
start = pd.Timestamp.now().normalize() - pd.DateOffset(years=np.random.randint(0, 30))

low_freq_index = pd.date_range(start=start, periods=low_periods, freq=low_freq)
high_freq = pd._libs.tslibs.to_offset(high_freq)
Expand All @@ -57,13 +55,9 @@ def generate_random_index_pair(
high_end = low_freq_index[-1] + high_freq * extra_high_freq_end

if high_periods is None:
high_periods = len(
pd.date_range(start=high_start, end=high_end, freq=high_freq)
)
high_periods = len(pd.date_range(start=high_start, end=high_end, freq=high_freq))

high_freq_index = pd.date_range(
start=high_start, periods=max(0, high_periods), freq=high_freq
)
high_freq_index = pd.date_range(start=high_start, periods=max(0, high_periods), freq=high_freq)

return (
pd.Series(1.0, index=low_freq_index, name="low_freq"),
Expand Down Expand Up @@ -135,20 +129,14 @@ def test_build_C_matrix(agg_func, frequencies):
full_size = 4 if high_name == "quarterly" else 12
full_size_mask = group_size.values == full_size
expected_result = groups.high_freq.agg(agg_func).values
np.testing.assert_allclose(
high_agg[full_size_mask], expected_result[full_size_mask]
)
np.testing.assert_allclose(high_agg[full_size_mask], expected_result[full_size_mask])

elif low_name == "quarterly":
groups = high_with_info.groupby(
["year", "quarter"]
) # .high_freq.agg(agg_func).values
groups = high_with_info.groupby(["year", "quarter"]) # .high_freq.agg(agg_func).values
group_size = groups.size()
full_size_mask = group_size.values == 3
expected_result = groups.high_freq.agg(agg_func).values
np.testing.assert_allclose(
high_agg[full_size_mask], expected_result[full_size_mask]
)
np.testing.assert_allclose(high_agg[full_size_mask], expected_result[full_size_mask])


class DisaggregationTests(unittest.TestCase):
Expand Down Expand Up @@ -232,9 +220,7 @@ def test_chow_lin_backcasting_error(self):
)

assert np.all(expected.index == m_chow_lin.index)
np.testing.assert_allclose(
expected.values.ravel(), m_chow_lin.values, rtol=1e-3
)
np.testing.assert_allclose(expected.values.ravel(), m_chow_lin.values, rtol=1e-3)

def test_chow_lin_backcasting_error_YtoQ(self):
expected = pd.read_csv("tests/data/AL_A_to_Q_expected.csv")
Expand Down Expand Up @@ -275,14 +261,10 @@ def test_chow_lin_backcasting_error_YtoQ(self):

assert res.success
assert np.all(expected.index == q_chow_lin.index)
np.testing.assert_allclose(
expected.values.ravel(), q_chow_lin.values.ravel(), rtol=1e-3
)
np.testing.assert_allclose(expected.values.ravel(), q_chow_lin.values.ravel(), rtol=1e-3)

def test_chow_lin_two_indicator(self):
expected = pd.read_csv(
"tests/data/R_output_chow_lin_two_indicator.csv", index_col=0
)
expected = pd.read_csv("tests/data/R_output_chow_lin_two_indicator.csv", index_col=0)
expected.index = self.exports_q.index
expected.columns = ["sales"]

Expand Down Expand Up @@ -327,9 +309,7 @@ def test_denton_cholette_w_constant(self):
self.assertEqual(expected, sales_q_dc.to_frame())

def test_denton_cholette_w_indicator(self):
expected = pd.read_csv(
"tests/data/R_output_denton_cholette_w_indicator.csv", index_col=0
)
expected = pd.read_csv("tests/data/R_output_denton_cholette_w_indicator.csv", index_col=0)
expected.index = self.exports_q.index
expected.columns = ["sales"]

Expand Down
22 changes: 7 additions & 15 deletions tests/test_frequency_conversion.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import calendar
import unittest

from typing import Callable, List, Tuple
from collections.abc import Callable

import pandas as pd

Expand All @@ -14,8 +14,8 @@

@composite
def freq(
draw: Callable[[SearchStrategy[int]], int], base: str, suffix_list: List[str]
) -> Tuple[str, str, str]:
draw: Callable[[SearchStrategy[int]], int], base: str, suffix_list: list[str]
) -> tuple[str, str, str]:
bases = [f"{base}E", f"B{base}E", f"{base}S", f"B{base}S"]
suffixes = [f"-{x}" for x in suffix_list] + [""]

Expand Down Expand Up @@ -51,9 +51,7 @@ def test_dataframe_merge(self, params):
low_freq_name, high_freq_name = get_frequency_names(low_freq_df, target_freq)

high_freq_df = pd.Series(1, index=index, name=high_freq_name)
result = pd.merge(
low_freq_df, high_freq_df, left_index=True, right_index=True, how="outer"
)
result = pd.merge(low_freq_df, high_freq_df, left_index=True, right_index=True, how="outer")

df, *_ = prepare_input_dataframes(low_freq_df, None, target_freq, "denton")
self.assertEqual(df.shape[0], result.shape[0])
Expand All @@ -71,9 +69,7 @@ def test_dataframe_merge_A_to_M(self, params):
low_freq_name, high_freq_name = get_frequency_names(low_freq_df, target_freq)

high_freq_df = pd.Series(1, index=index, name=high_freq_name)
result = pd.merge(
low_freq_df, high_freq_df, left_index=True, right_index=True, how="outer"
)
result = pd.merge(low_freq_df, high_freq_df, left_index=True, right_index=True, how="outer")

df, *_ = prepare_input_dataframes(low_freq_df, None, target_freq, "denton")

Expand All @@ -92,9 +88,7 @@ def test_dataframe_merge_Q_to_M(self):
low_freq_name, high_freq_name = get_frequency_names(low_freq_df, target_freq)

high_freq_df = pd.Series(1, index=index, name=high_freq_name)
result = pd.merge(
low_freq_df, high_freq_df, left_index=True, right_index=True, how="outer"
)
result = pd.merge(low_freq_df, high_freq_df, left_index=True, right_index=True, how="outer")

df, df_low, df_high, factor = prepare_input_dataframes(
low_freq_df, None, target_freq, "denton"
Expand Down Expand Up @@ -125,9 +119,7 @@ def test_build_conversion_matrix():
df.iloc[:, 0].dropna().values
df.drop(columns=df.columns[0]).values

C = build_conversion_matrix(
low_freq_df, high_freq_df, time_conversion_factor, agg_func="sum"
)
C = build_conversion_matrix(low_freq_df, high_freq_df, time_conversion_factor, agg_func="sum")

assert C.shape[0] == low_freq_df.shape[0]
assert C.shape[1] == high_freq_df.shape[0]
Expand Down
Loading

0 comments on commit 4ca0ea7

Please sign in to comment.