11import numpy as np
22import pandas as pd
3+ from datetime import datetime
34from sklearn .preprocessing import StandardScaler , PolynomialFeatures
45from sklearn .pipeline import make_pipeline
56import statsmodels .api as sm
67from statsmodels .sandbox .regression .predstd import wls_prediction_std
78
89
9- def check_tolerance (t , y , to_exclude : int = 1 , poly_features : list = [1 , 2 ], alpha : float = 0.05 , predict_all : bool = False ) -> pd .DataFrame :
10+ def check_tolerance (t , y , to_exclude : int = 1 , poly_features : list = [1 , 2 ], alpha : float = 0.05 , parse_dates : bool = False , predict_all : bool = False ) -> pd .DataFrame :
1011 """
1112 Check that some future values are within a weighted least squares confidence interval.
1213
1314 Parameters
1415 ----------
15- t : np.array
16+ t : pd.Series
1617 N explanatory time points of shape (N, 1).
17- y : np.array
18+ y : pd.Series
1819 The corresponding response variable values to X, of shape (N, 1).
1920 to_exclude : int, default = 1
2021 How many of the last y values will have their tolerances checked.
@@ -24,6 +25,8 @@ def check_tolerance(t, y, to_exclude: int = 1, poly_features: list = [1, 2], alp
2425 a second degree polynomial to the data and return both sets of results.
2526 alpha : float, default = 0.05
2627 Alpha parameter for the weighted least squares confidence interval.
28+ parse_dates : bool, default = True
29+ Set to true to parse string dates in t
2730 predict_all : bool, default = False
2831 Set to true to show predictions for all points of the dataset.
2932
@@ -32,6 +35,7 @@ def check_tolerance(t, y, to_exclude: int = 1, poly_features: list = [1, 2], alp
3235 -------
3336 pd.DataFrame
3437 DataFrame containing:
38+ "t" : Value for t
3539 "yhat_u" : Upper condfidence interval for y
3640 "yobs" : Observed value for y
3741 "yhat" : Predicted value for y
@@ -42,15 +46,15 @@ def check_tolerance(t, y, to_exclude: int = 1, poly_features: list = [1, 2], alp
4246 Examples
4347 --------
4448 >>> check_tolerance(
45- ... t = np.array ([1001,1002,1003,1004,1005,1006]),
46- ... y = np.array ([2,3,4,4.5,5,5.1]),
49+ ... t = pd.Series ([1001,1002,1003,1004,1005,1006]),
50+ ... y = pd.Series ([2,3,4,4.5,5,5.1]),
4751 ... to_exclude = 2,
4852 ... )
49- yhat_u yobs yhat yhat_l polynomial
50- 0 6.817413 5.0 5.500 4.182587 1
51- 1 7.952702 5.1 6.350 4.747298 1
52- 2 9.077182 5.0 4.875 0.672818 2
53- 3 13.252339 5.1 4.975 -3.302339 2
53+ t yhat_u yobs yhat yhat_l polynomial
54+ 0 1005 6.817413 5.0 5.500 4.182587 1
55+ 1 1006 7.952702 5.1 6.350 4.747298 1
56+ 2 1005 9.077182 5.0 4.875 0.672818 2
57+ 3 1006 13.252339 5.1 4.975 -3.302339 2
5458 """
5559
5660 if not isinstance (poly_features , list ):
@@ -66,21 +70,27 @@ def check_tolerance(t, y, to_exclude: int = 1, poly_features: list = [1, 2], alp
6670 """The sample size for your model is smaller than 4. This will not produce a good
6771 model. Either reduce to_exclude or increase your sample size to continue."""
6872 )
69- assert np . isfinite ( y ).all (), (
73+ assert y . notna ( ).all (), (
7074 f"""Your sample contains missing or infinite values for y at locations
7175 { list (map (tuple , np .where (np .isnan (y ))))} . Exclude these values to continue."""
7276 )
73- assert np . isfinite ( t ).all (), (
77+ assert t . notna ( ).all (), (
7478 f"""Your sample contains missing or infinite values for t at locations
7579 { list (map (tuple , np .where (np .isnan (t ))))} . Exclude these values to continue."""
7680 )
7781
82+ # Convert date strings to numeric variables for the model
83+ if parse_dates :
84+ t_numeric = pd .to_datetime (t )
85+ t_numeric = (t_numeric - datetime (1970 , 1 , 1 )) \
86+ .apply (lambda x : x .days )
7887
79- # Sort data by X increasing
80- idx = np .argsort (t )
88+ # Sort data by t increasing. t_ is for internal use.
89+ idx = np .argsort (t_numeric .values ) if parse_dates else np .argsort (t .values )
90+ t_ = t_numeric [idx ] if parse_dates else t [idx ]
8191 t = t [idx ]
8292 y = y [idx ]
83-
93+
8494 results = pd .DataFrame ()
8595 for degree in poly_features :
8696 transforms = make_pipeline (
@@ -89,13 +99,14 @@ def check_tolerance(t, y, to_exclude: int = 1, poly_features: list = [1, 2], alp
8999 )
90100
91101 # Fit transforms to training data only, apply to all data.
92- fitted_transforms = transforms .fit (t [:- to_exclude ].reshape (- 1 , 1 ))
93- _t = fitted_transforms .transform (t .reshape (- 1 , 1 ))
102+ fitted_transforms = transforms .fit (t_ [:- to_exclude ]. values .reshape (- 1 , 1 ))
103+ t_scaled = fitted_transforms .transform (t_ . values .reshape (- 1 , 1 ))
94104
95- t_train , y_train = _t [:- to_exclude , :], y [:- to_exclude ]
96- t_predict , y_predict = (
97- _t if predict_all else _t [- to_exclude :, :],
98- y if predict_all else y [- to_exclude :]
105+ t_train , y_train = t_scaled [:- to_exclude , :], y [:- to_exclude ]
106+ t_predict , y_predict , t_orig = (
107+ t_scaled if predict_all else t_scaled [- to_exclude :, :],
108+ y if predict_all else y [- to_exclude :],
109+ t if predict_all else t [- to_exclude :],
99110 )
100111
101112 # Fit ordinary least squares model to the training data, then predict for the
@@ -109,6 +120,7 @@ def check_tolerance(t, y, to_exclude: int = 1, poly_features: list = [1, 2], alp
109120 # Store model results in master frame
110121 results = results .append (
111122 pd .DataFrame ({
123+ "t" : t_orig ,
112124 "yhat_u" : yhat_u ,
113125 "yobs" : y_predict ,
114126 "yhat" : yhat ,
0 commit comments