Files
2022-07-02 22:37:44 +02:00

334 lines
8.7 KiB
Python

# -*- coding: utf-8 -*-
from functools import reduce
from math import floor as mfloor
from operator import mul
from sys import float_info as sflt
from numpy import all, append, array, corrcoef, dot, exp, fabs
from numpy import log, nan, ndarray, ones, seterr, sign, sqrt, sum, triu
from pandas import DataFrame, Series
from pandas_ta._typing import (
Array,
DictLike,
Float,
Int,
IntFloat,
List,
Optional
)
from pandas_ta.maps import Imports
from pandas_ta.utils._validate import v_series
__all__ = [
'fibonacci',
'erf',
'combination',
'geometric_mean',
'hpoly',
'linear_regression',
'log_geometric_mean',
'pascals_triangle',
'strided_window',
'symmetric_triangle',
'weights',
'zero',
'df_error_analysis',
]
def combination(
n: Int = 1, r: Int = 0,
repetition: bool = False, multichoose: bool = False
) -> Int:
"""https://stackoverflow.com/questions/4941753/is-there-a-math-ncr-function-in-python"""
n, r = int(fabs(n)), int(fabs(r))
if repetition or multichoose:
n = n + r - 1
# if r < 0: return None
r = min(n, n - r)
if r == 0:
return 1
numerator = reduce(mul, range(n, n - r, -1), 1)
denominator = reduce(mul, range(1, r + 1), 1)
return numerator // denominator
def erf(x: IntFloat) -> Float:
"""Error Function erf(x)
The algorithm comes from Handbook of Mathematical Functions, formula 7.1.26.
Source: https://stackoverflow.com/questions/457408/is-there-an-easily-available-implementation-of-erf-for-python
"""
x_sign = sign(x)
x = abs(x)
# constants
a1 = 0.254829592
a2 = -0.284496736
a3 = 1.421413741
a4 = -1.453152027
a5 = 1.061405429
p = 0.3275911
# A&S formula 7.1.26
t = 1.0 / (1.0 + p * x)
y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2)
* t + a1) * t * exp(-x * x)
return x_sign * y # erf(-x) = -erf(x)
def fibonacci(
n: Int = 2, weighted: bool = False, zero: bool = False
) -> Array:
"""Fibonacci Sequence as a numpy array"""
n = int(fabs(n)) if n >= 0 else 2
if zero:
a, b = 0, 1
else:
n -= 1
a, b = 1, 1
result = array([a])
for _ in range(0, n):
a, b = b, a + b
result = append(result, a)
if weighted:
fib_sum = sum(result)
if fib_sum > 0:
return result / fib_sum
else:
return result
else:
return result
def geometric_mean(series: Series) -> Float:
"""Returns the Geometric Mean for a Series of positive values."""
n = series.size
if n < 1:
return series.iloc[0]
has_zeros = 0 in series.values
if has_zeros:
series = series.fillna(0) + 1
if all(series > 0):
mean = series.prod() ** (1 / n)
return mean if not has_zeros else mean - 1
return 0
def hpoly(c: Array, x: IntFloat) -> Float:
"""Horner Calculation for Polynomial Evaluation (hpoly)
array: np.array of polynomial coefficients
* Convert list or Series to np.array prior to calling the method for
best performance
x: value to evaluate
Example:
coeffs_0 = [4, -3, 0, 1] # 4x^3 - 3x^2 + 0x + 1
coeffs_1 = np.array(coeffs_0) # Faster
coeffs_2 = pd.Series(coeffs_0).values
x = -6.5
hpoly(coeffs_0, x) => -1224.25
hpoly(coeffs_1, x) or hpoly(coeffs_2, x) => -1224.25 # Faster
"""
if not isinstance(c, ndarray):
c_ = array(c)
m, y = c_.size, c_[0]
for i in range(1, m):
y = c_[i] + x * y
return y
def linear_regression(x: Series, y: Series) -> DictLike:
"""Classic Linear Regression in Numpy or Scikit-Learn"""
x, y = v_series(x), v_series(y)
m, n = x.size, y.size
if m != n:
print(f"[X] X and y have unequal sizes: {m} != {n}")
return {}
if Imports["sklearn"]:
return _linear_regression_sklearn(x, y)
else:
return _linear_regression_np(x, y)
def log_geometric_mean(series: Series) -> Float:
"""Returns the Logarithmic Geometric Mean"""
n = series.size
if n > 1:
series = series.fillna(0) + 1
if all(series > 0):
return exp(log(series).sum() / n) - 1
return 0
def pascals_triangle(
n: Int = None, inverse: bool = False, weighted: bool = False
) -> Array:
"""Pascal's Triangle
Returns a numpy array of the nth row of Pascal's Triangle.
n=4 => triangle: [1, 4, 6, 4, 1]
=> weighted: [0.0625, 0.25, 0.375, 0.25, 0.0625]
=> inverse weighted: [0.9375, 0.75, 0.625, 0.75, 0.9375]
"""
n = int(fabs(n)) if n is not None else 0
# Calculation
triangle = array([combination(n=n, r=i) for i in range(0, n + 1)])
triangle_sum = sum(triangle)
triangle_weights = triangle / triangle_sum
inverse_weights = 1 - triangle_weights
if weighted and inverse:
return inverse_weights
if weighted:
return triangle_weights
if inverse:
return None
return triangle
def strided_window(x: Array, length: Int) -> Array:
"""as_strided
creates a view into the array given the exact strides and shape.
* Recommended to avoid when possible.
Source: https://numpy.org/devdocs/reference/generated/numpy.lib.stride_tricks.as_strided.html
Pandas TA Issue: https://github.com/twopirllc/pandas-ta/issues/285
"""
from numpy.lib.stride_tricks import as_strided
strides = x.strides + (x.strides[-1],)
shape = x.shape[:-1] + (x.shape[-1] - length + 1, length)
return as_strided(x, shape=shape, strides=strides, writeable=False)
def symmetric_triangle(
n: Int = None, weighted: bool = False
) -> Optional[List[int]]:
"""Symmetric Triangle with n >= 2
Returns a numpy array of the nth row of Symmetric Triangle.
n=4 => triangle: [1, 2, 2, 1]
=> weighted: [0.16666667 0.33333333 0.33333333 0.16666667]
"""
n = int(fabs(n)) if n is not None else 2
triangle = None
if n == 2:
triangle = [1, 1]
if n > 2:
if n % 2 == 0:
front = [i + 1 for i in range(0, mfloor(n / 2))]
triangle = front + front[::-1]
else:
front = [i + 1 for i in range(0, mfloor(0.5 * (n + 1)))]
triangle = front.copy()
front.pop()
triangle += front[::-1]
if weighted and isinstance(triangle, list):
return triangle / sum(triangle)
return triangle
def weights(w: Array):
"""Calculates the dot product of weights with values x"""
def _dot(x):
return dot(w, x)
return _dot
def zero(x: IntFloat) -> IntFloat:
"""If the value is close to zero, then return zero.
Otherwise return itself."""
return 0 if abs(x) < sflt.epsilon else x
# TESTING
def df_error_analysis(
A: DataFrame, B: DataFrame,
plot: bool = False, triangular: bool = False,
method: str = "pearson",
) -> DataFrame:
"""DataFrame Correlation Analysis helper"""
_r_method = ["pearson", "kendall", "spearman"]
corr_method = method if method in _r_method else _r_method[0]
# Find their differences and correlation
diff = A - B
result = A.corr(B, method=corr_method)
# For plotting
if plot:
diff.hist()
if diff[diff > 0].any():
diff.plot(kind="kde")
if triangular:
return result.where(triu(ones(result.shape)).astype(bool))
return result
# PRIVATE
def _linear_regression_np(x: Series, y: Series) -> DictLike:
"""Simple Linear Regression in Numpy
for two 1d arrays for environments without the sklearn package."""
result = {"a": nan, "b": nan, "r": nan, "t": nan, "line": nan}
x_sum = x.sum()
y_sum = y.sum()
if int(x_sum) != 0:
# 1st row, 2nd col value corr(x, y)
r = corrcoef(x, y)[0, 1]
m = x.size
r_mix = m * (x * y).sum() - x_sum * y_sum
b = r_mix // (m * (x * x).sum() - x_sum * x_sum)
a = y.mean() - b * x.mean()
line = a + b * x
_np_err = seterr()
seterr(divide="ignore", invalid="ignore")
result = {
"a": a, "b": b, "r": r,
"t": r / sqrt((1 - r * r) / (m - 2)),
"line": line,
}
seterr(divide=_np_err["divide"], invalid=_np_err["invalid"])
return result
def _linear_regression_sklearn(x: Series, y: Series) -> DictLike:
"""Simple Linear Regression in Scikit Learn for two 1d arrays for
environments with the sklearn package."""
from sklearn.linear_model import LinearRegression
X = DataFrame(x)
lr = LinearRegression().fit(X, y=y)
r = lr.score(X, y=y)
a, b = lr.intercept_, lr.coef_[0]
result = {
"a": a, "b": b, "r": r,
"t": r / sqrt((1 - r * r) / (x.size - 2)),
"line": a + b * x
}
return result