Skip to content
This repository has been archived by the owner on Dec 16, 2023. It is now read-only.

Commit

Permalink
Release/0.7.0 (#86)
Browse files Browse the repository at this point in the history
* MAINT: Drop Python 3.6 (close #68) (#69)

  * Python 3.6 is no longer supported. Please update to >=3.7.

* MAINT: Add typing (#77)

* MAINT: Update README.md (close #81) (#82) (#83)

* MAINT: Rename mode 'full' to 'same' (close #55) (#84)

* CHORE: Update pytest-cov requirement from ^2.8.1 to ^3.0.0 (#78)

* CHORE: Update Makefile (#88)

Co-authored-by: GitHub Actions <action@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: GitHub Actions <action@github.com>
  • Loading branch information
4 people committed Oct 31, 2021
1 parent 799ed3d commit 0d034b6
Show file tree
Hide file tree
Showing 16 changed files with 128 additions and 106 deletions.
6 changes: 4 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:

strategy:
matrix:
python-version: ['3.6', '3.7', '3.8', '3.9']
python-version: ['3.7', '3.8', '3.9']

steps:

Expand Down Expand Up @@ -58,10 +58,12 @@ jobs:
- uses: actions/checkout@v2

- uses: actions/setup-python@v2
with:
python-version: '3.9'

- run: pip install poetry && poetry install --extras torch

- run: make typecheck
- run: make type

lint:

Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/doc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ jobs:
- uses: actions/checkout@v2

- uses: actions/setup-python@v2
with:
python-version: '3.9'

- run: pip install poetry && poetry install --extras torch

Expand Down
20 changes: 11 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
PROJECT_NAME := fracdiff
RUN := poetry run

.PHONY: check
check: test lint
check: test lint type

.PHONY: install
install:
Expand All @@ -12,40 +13,41 @@ test: test-doctest test-pytest

.PHONY: test-doctest
test-doctest:
@poetry run pytest --doctest-modules $(PROJECT_NAME)
$(RUN) pytest --doctest-modules $(PROJECT_NAME)

.PHONY: test-pytest
test-pytest:
@poetry run pytest --doctest-modules --cov=$(PROJECT_NAME) tests
$(RUN) pytest --doctest-modules --cov=$(PROJECT_NAME) tests

.PHONY: lint
lint: lint-black lint-isort

.PHONY: lint-black
lint-black:
@poetry run python3 -m black --check .
$(RUN) python3 -m black --check --quiet .

.PHONY: lint-isort
lint-isort:
@poetry run python3 -m isort --check --force-single-line-imports .
$(RUN) run python3 -m isort --check --force-single-line-imports --quiet .

.PHONY: format
format: format-black format-isort

.PHONY: format-black
format-black:
@poetry run python3 -m black --quiet .
$(RUN) python3 -m black --quiet .

.PHONY: format-isort
format-isort:
@poetry run python3 -m isort --force-single-line-imports --quiet .
$(RUN) python3 -m isort --force-single-line-imports --quiet .

.PHONY: doc
doc:
@cd docs && make html

.PHONY: typecheck
@poetry run mypy $(PROJECT_NAME)
.PHONY: type
type:
$(RUN) mypy $(PROJECT_NAME)

.PHONY: publish
publish:
Expand Down
28 changes: 18 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,24 @@ See [M. L. Prado, "Advances in Financial Machine Learning"][prado].
## Installation

```sh
$ pip install fracdiff
pip install fracdiff
```

## Features

### Functionalities

- [`fdiff`](https://fracdiff.github.io/fracdiff/generated/fracdiff.fdiff.html): A function that extends [`numpy.diff`](https://numpy.org/doc/stable/reference/generated/numpy.diff.html) to fractional differentiation.
- [`Fracdiff`](https://fracdiff.github.io/fracdiff/generated/fracdiff.Fracdiff.html): A scikit-learn [transformer](https://scikit-learn.org/stable/modules/generated/sklearn.base.TransformerMixin.html) to compute fractional differentiation.
- [`FracdiffStat`](https://fracdiff.github.io/fracdiff/generated/fracdiff.FracdiffStat.html): `Fracdiff` plus automatic choice of differentiation order that makes time-series stationary.
- [`torch.fdiff`](https://fracdiff.github.io/fracdiff/generated/fracdiff.torch.fdiff.html): A functional that extends [`torch.diff`](https://pytorch.org/docs/stable/generated/torch.diff.html) to fractional differentiation.
- [`torch.Fracdiff`](https://fracdiff.github.io/fracdiff/generated/fracdiff.torch.Fracdiff.html): A module that computes fractional differentiation.
- [`fdiff`][doc-fdiff]: A function that extends [`numpy.diff`](https://numpy.org/doc/stable/reference/generated/numpy.diff.html) to fractional differentiation.
- [`sklearn.Fracdiff`][doc-sklearn.Fracdiff]: A scikit-learn [transformer](https://scikit-learn.org/stable/modules/generated/sklearn.base.TransformerMixin.html) to compute fractional differentiation.
- [`sklearn.FracdiffStat`][doc-sklearn.FracdiffStat]: `Fracdiff` plus automatic choice of differentiation order that makes time-series stationary.
- [`torch.fdiff`][doc-torch.fdiff]: A functional that extends [`torch.diff`](https://pytorch.org/docs/stable/generated/torch.diff.html) to fractional differentiation.
- [`torch.Fracdiff`][doc-torch.Fracdiff]: A module that computes fractional differentiation.

[doc-fdiff]: https://fracdiff.github.io/fracdiff/generated/fracdiff.fdiff.html
[doc-sklearn.Fracdiff]: https://fracdiff.github.io/fracdiff/generated/fracdiff.sklearn.Fracdiff.html
[doc-sklearn.FracdiffStat]: https://fracdiff.github.io/fracdiff/generated/fracdiff.sklearn.FracdiffStat.html
[doc-torch.fdiff]: https://fracdiff.github.io/fracdiff/generated/fracdiff.torch.fdiff.html
[doc-torch.Fracdiff]: https://fracdiff.github.io/fracdiff/generated/fracdiff.torch.Fracdiff.html

### Speed

Expand Down Expand Up @@ -92,12 +98,14 @@ fdiff(a, 0.5, axis=-1)
# [0. , 5. , 3.5 , 4.375 ]])
```

### Preprocessing by fractional differentiation
### Scikit-learn API

#### Preprocessing by fractional differentiation

A transformer class [`Fracdiff`](https://fracdiff.github.io/fracdiff/#id1) performs fractional differentiation by its method `transform`.

```python
from fracdiff import Fracdiff
from fracdiff.sklearn import Fracdiff

X = ... # 2d time-series with shape (n_samples, n_features)

Expand Down Expand Up @@ -127,14 +135,14 @@ pipeline = Pipeline([
pipeline.fit(X, y)
```

### Fractional differentiation while preserving memory
#### Fractional differentiation while preserving memory

A transformer class [`FracdiffStat`](https://fracdiff.github.io/fracdiff/#fracdiffstat) finds the minumum order of fractional differentiation that makes time-series stationary.
Differentiated time-series with this order is obtained by subsequently applying `transform` method.
This series is interpreted as a stationary time-series keeping the maximum memory of the original time-series.

```python
from fracdiff import FracdiffStat
from fracdiff.sklearn import FracdiffStat

X = ... # 2d time-series with shape (n_samples, n_features)

Expand Down
37 changes: 27 additions & 10 deletions fracdiff/fdiff.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from functools import partial
from typing import Optional

# found module but no type hints or library stubs
import numpy as np # type: ignore
Expand All @@ -7,7 +8,7 @@
from scipy.special import binom # type: ignore


def fdiff_coef(d, window) -> np.ndarray:
def fdiff_coef(d: float, window: int) -> np.ndarray:
"""Returns sequence of coefficients in fracdiff operator.
Parameters
Expand Down Expand Up @@ -35,7 +36,13 @@ def fdiff_coef(d, window) -> np.ndarray:


def fdiff(
a, n=1.0, axis=-1, prepend=np._NoValue, append=np._NoValue, window=10, mode="full"
a: np.ndarray,
n: float = 1.0,
axis: int = -1,
prepend: Optional[np.ndarray] = None,
append: Optional[np.ndarray] = None,
window: int = 10,
mode: str = "same",
) -> np.ndarray:
"""Calculate the `n`-th differentiation along the given axis.
Expand All @@ -59,13 +66,14 @@ def fdiff(
Values to append.
window : int, default=10
Number of observations to compute each element in the output.
mode : {"full", "valid"}, default="full"
"full" (default) :
Return elements where at least one coefficient of fracdiff is used.
mode : {"same", "valid"}, default="same"
"same" (default) :
At the beginning of the time series,
return elements where at least one coefficient of fracdiff is used.
Output size along ``axis`` is :math:`L_{\\mathrm{in}}`
where :math:`L_{\\mathrm{in}}` is the length of ``a`` along ``axis``
(plus the lengths of ``append`` and ``prepend``).
Boundary effects may be seen at the At the beginning of a time-series.
Boundary effects may be seen at the at the beginning of a time-series.
"valid" :
Return elements where all coefficients of fracdiff are used.
Output size along ``axis`` is
Expand Down Expand Up @@ -114,18 +122,26 @@ def fdiff(
array([[ 1. , 3. , 6. , 10. , 15. ],
[-0.5, 3.5, 3. , 3. , 3.5]])
"""
if mode == "full":
mode = "same"
raise DeprecationWarning("mode 'full' was renamed to 'same'.")

if isinstance(n, int) or n.is_integer():
prepend = np._NoValue if prepend is None else prepend # type: ignore
append = np._NoValue if append is None else append # type: ignore
return np.diff(a, n=int(n), axis=axis, prepend=prepend, append=append)

if a.ndim == 0:
raise ValueError("diff requires input that is at least one dimensional")

a = np.asanyarray(a)
axis = np.core.multiarray.normalize_axis_index(axis, a.ndim)
# Mypy complains:
# fracdiff/fdiff.py:135: error: Module has no attribute "normalize_axis_index"
axis = np.core.multiarray.normalize_axis_index(axis, a.ndim) # type: ignore
dtype = a.dtype if np.issubdtype(a.dtype, np.floating) else np.float64

combined = []
if prepend is not np._NoValue:
if prepend is not None:
prepend = np.asanyarray(prepend)
if prepend.ndim == 0:
shape = list(a.shape)
Expand All @@ -135,7 +151,7 @@ def fdiff(

combined.append(a)

if append is not np._NoValue:
if append is not None:
append = np.asanyarray(append)
if append.ndim == 0:
shape = list(a.shape)
Expand All @@ -149,7 +165,8 @@ def fdiff(
if mode == "valid":
D = partial(np.convolve, fdiff_coef(n, window).astype(dtype), mode="valid")
a = np.apply_along_axis(D, axis, a)
elif mode == "full":
elif mode == "same":
# Convolve with the mode 'full' and cut last
D = partial(np.convolve, fdiff_coef(n, window).astype(dtype), mode="full")
s = tuple(
slice(a.shape[axis]) if i == axis else slice(None) for i in range(a.ndim)
Expand Down
8 changes: 4 additions & 4 deletions fracdiff/sklearn/fracdiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class Fracdiff(TransformerMixin):
The order of differentiation.
window : int > 0 or None, default 10
Number of observations to compute each element in the output.
mode : {"full", "valid"}, default "full"
mode : {"same", "valid"}, default "same"
See :func:`fracdiff.fdiff` for details.
window_policy : {"fixed"}, default "fixed"
"fixed" (default) :
Expand Down Expand Up @@ -65,7 +65,7 @@ class Fracdiff(TransformerMixin):
[-0.0625]])
"""

def __init__(self, d=1.0, window=10, mode="full", window_policy="fixed"):
def __init__(self, d=1.0, window=10, mode="same", window_policy="fixed"):
self.d = d
self.window = window
self.mode = mode
Expand All @@ -76,7 +76,7 @@ def __repr__(self):
Examples
--------
>>> Fracdiff(0.5)
Fracdiff(d=0.5, window=10, mode=full, window_policy=fixed)
Fracdiff(d=0.5, window=10, mode=same, window_policy=fixed)
"""
name = self.__class__.__name__
attrs = ["d", "window", "mode", "window_policy"]
Expand Down Expand Up @@ -104,7 +104,7 @@ def fit(self, X, y=None):
self.coef_ = fdiff_coef(self.d, self.window)
return self

def transform(self, X, y=None) -> numpy.array:
def transform(self, X, y=None) -> numpy.ndarray:
"""
Return the fractional differentiation of `X`.
Expand Down
4 changes: 2 additions & 2 deletions fracdiff/sklearn/fracdiffstat.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class FracdiffStat(TransformerMixin, BaseEstimator):
----------
window : int > 0 or None, default 10
Number of observations to compute each element in the output.
mode : {"full", "valid"}, default "full"
mode : {"same", "valid"}, default "same"
See :func:`fracdiff.fdiff` for details.
window_policy : {"fixed"}, default "fixed"
If "fixed" :
Expand Down Expand Up @@ -76,7 +76,7 @@ class FracdiffStat(TransformerMixin, BaseEstimator):
def __init__(
self,
window=10,
mode="full",
mode="same",
window_policy="fixed",
stattest="ADF",
pvalue=0.05,
Expand Down
7 changes: 4 additions & 3 deletions fracdiff/sklearn/stat.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import numpy as np
import statsmodels.tsa.stattools as stattools # type: ignore


Expand Down Expand Up @@ -31,7 +32,7 @@ class StatTester:
False
"""

def __init__(self, method="ADF"):
def __init__(self, method: str = "ADF"):
self.method = method

@property
Expand All @@ -41,7 +42,7 @@ def null_hypothesis(self) -> str:
else:
raise ValueError(f"Unknown method: {self.method}")

def pvalue(self, x) -> float:
def pvalue(self, x: np.ndarray) -> float:
"""
Return p-value of the stationarity test.
Expand All @@ -61,7 +62,7 @@ def pvalue(self, x) -> float:
else:
raise ValueError(f"Unknown method: {self.method}")

def is_stat(self, x, pvalue=0.05) -> bool:
def is_stat(self, x: np.ndarray, pvalue: float = 0.05) -> bool:
"""
Return whether stationarity test implies stationarity.
Expand Down
10 changes: 6 additions & 4 deletions fracdiff/sklearn/tol.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from fracdiff.fdiff import fdiff_coef


def window_from_tol_coef(n, tol_coef, max_window=2 ** 12) -> int:
def window_from_tol_coef(n: float, tol_coef: float, max_window: int = 2 ** 12) -> int:
"""
Return length of window determined from tolerance to memory loss.
Expand Down Expand Up @@ -40,10 +40,12 @@ def window_from_tol_coef(n, tol_coef, max_window=2 ** 12) -> int:
-0.0625
"""
coef = np.abs(fdiff_coef(n, max_window))
return np.searchsorted(-coef, -tol_coef) + 1 # index -> length
return int(np.searchsorted(-coef, -tol_coef) + 1) # index -> length


def window_from_tol_memory(n, tol_memory, max_window=2 ** 12) -> int:
def window_from_tol_memory(
n: float, tol_memory: float, max_window: int = 2 ** 12
) -> int:
"""
Return length of window determined from tolerance to memory loss.
Expand Down Expand Up @@ -78,4 +80,4 @@ def window_from_tol_memory(n, tol_memory, max_window=2 ** 12) -> int:
-0.20383...
"""
lost_memory = np.abs(np.cumsum(fdiff_coef(n, max_window)))
return np.searchsorted(-lost_memory, -tol_memory) + 1 # index -> length
return int(np.searchsorted(-lost_memory, -tol_memory) + 1) # index -> length
3 changes: 1 addition & 2 deletions fracdiff/torch/functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from fracdiff.fdiff import fdiff_coef as fdiff_coef_numpy


def fdiff_coef(d: float, window: int) -> torch.Tensor:
def fdiff_coef(d: float, window: int) -> Tensor:
"""Returns sequence of coefficients in fracdiff operator.
Args:
Expand Down Expand Up @@ -59,7 +59,6 @@ def fdiff(
increases by the number of elements in each of these tensors.
Examples:
>>> from fracdiff.torch import fdiff
>>> input = torch.tensor([1, 2, 4, 7, 0])
>>> fdiff(input, 0.5, mode="same", window=3)
Expand Down

0 comments on commit 0d034b6

Please sign in to comment.