@@ -52,31 +52,23 @@ public class BoxCox
5252 /// Fit the transformation parameters using maximum likelihood estimation.
5353 /// </summary>
5454 /// <param name="values">The list of values to transform.</param>
55- /// <param name="lambda1">Output. The transformation exponent. Range -5 to +5.</param>
56- /// <param name="lambda2">Output. The transformation shift for negative values.</param>
55+ /// <param name="lambda">Output. The transformation exponent. Range -5 to +5.</param>
5756 /// <remarks>
5857 /// https://www.rdocumentation.org/packages/EnvStats/versions/2.4.0/topics/boxcox
5958 /// </remarks>
60- public static void FitLambda ( IList < double > values , out double lambda1 , out double lambda2 )
59+ public static void FitLambda ( IList < double > values , out double lambda )
6160 {
62- int n = values . Count ;
63- double l1 = 0d ;
64- double l2 = 0d ;
65- double min = Statistics . Minimum ( values ) ;
66- if ( min <= 0d ) l2 = 0d - min + Tools . DoubleMachineEpsilon ;
67-
68- Func < double , double > func = lambda =>
69- {
70- return LogLikelihood ( values , lambda , l2 ) ;
71- } ;
72-
7361 // Solve with Brent
74- var brent = new BrentSearch ( func , - 5d , 5d ) ;
62+ var brent = new BrentSearch ( ( x ) => { return LogLikelihood ( values , x ) ; } , - 5d , 5d ) ;
7563 brent . Maximize ( ) ;
76- l1 = brent . BestParameterSet . Values [ 0 ] ;
77- // Set parameters
78- lambda1 = l1 ;
79- lambda2 = l2 ;
64+ if ( brent . Status == OptimizationStatus . Success )
65+ {
66+ lambda = brent . BestParameterSet . Values [ 0 ] ;
67+ }
68+ else
69+ {
70+ lambda = double . NaN ;
71+ }
8072 }
8173
8274 /// <summary>
@@ -85,21 +77,20 @@ public static void FitLambda(IList<double> values, out double lambda1, out doubl
8577 /// </summary>
8678 /// <param name="values">The list of values to transform.</param>
8779 /// <param name="lambda1">The transformation exponent. Range -5 to +5.</param>
88- /// <param name="lambda2">The transformation shift for negative values.</param>
8980 /// <returns>
9081 /// The value of log-likelihood function evaluated at the given values and lambdas.
9182 /// </returns>
92- public static double LogLikelihood ( IList < double > values , double lambda1 , double lambda2 )
83+ public static double LogLikelihood ( IList < double > values , double lambda1 )
9384 {
9485 int n = values . Count ;
9586 var y = new double [ n ] ;
9687 double mu = 0d ;
9788 var sumX = 0d ;
9889 for ( int i = 0 ; i < n ; i ++ )
9990 {
100- y [ i ] = Transform ( values [ i ] , lambda1 , lambda2 ) ;
91+ y [ i ] = Transform ( values [ i ] , lambda1 ) ;
10192 mu += y [ i ] ;
102- sumX += Math . Log ( values [ i ] + lambda2 ) ;
93+ sumX += Math . Log ( values [ i ] ) ;
10394 }
10495 mu = mu / n ;
10596 double sse = 0d ;
@@ -110,57 +101,78 @@ public static double LogLikelihood(IList<double> values, double lambda1, double
110101 return ll ;
111102 }
112103
104+ /// <summary>
105+ /// Computes the Log-Jacobian used to adjust the log-likelihood function.
106+ /// </summary>
107+ /// <param name="values">The list of values to transform.</param>
108+ /// <param name="lambda">The transformation exponent. Range -5 to +5.</param>
109+ /// <returns>Returns the Log Jacobian.</returns>
110+ public static double LogJacobian ( IList < double > values , double lambda )
111+ {
112+ double logJacobianSum = 0d ;
113+ int n = values . Count ;
114+
115+ for ( int i = 0 ; i < n ; i ++ )
116+ {
117+ double xi = values [ i ] ;
118+
119+ if ( xi <= 0 )
120+ return double . NegativeInfinity ; // Box-Cox undefined for non-positive values
121+
122+ // For Box-Cox: log derivative is (lambda - 1) * log(x)
123+ logJacobianSum += ( lambda - 1d ) * Math . Log ( xi ) ;
124+ }
125+
126+ return logJacobianSum ;
127+ }
128+
113129 /// <summary>
114130 /// Returns the Box-Cox transformation of the value.
115131 /// </summary>
116132 /// <param name="value">The value to transform.</param>
117- /// <param name="lambda1">The transformation exponent. Range -5 to +5.</param>
118- /// <param name="lambda2">The transformation shift for negative values.</param>
119- public static double Transform ( double value , double lambda1 , double lambda2 = 0.0d )
133+ /// <param name="lambda">The transformation exponent. Range -5 to +5.</param>
134+ public static double Transform ( double value , double lambda )
120135 {
121- if ( Math . Abs ( lambda1 ) > 5d ) return double . NaN ;
122- if ( lambda1 == 0d ) return Math . Log ( value + lambda2 ) ;
123- return ( Math . Pow ( value + lambda2 , lambda1 ) - 1.0d ) / lambda1 ;
136+ if ( Math . Abs ( lambda ) > 5d ) return double . NaN ;
137+ if ( Math . Abs ( lambda ) < 1e-8 ) return Math . Log ( value ) ;
138+ return ( Math . Pow ( value , lambda ) - 1.0d ) / lambda ;
124139 }
125140
126141 /// <summary>
127142 /// Returns the Box-Cox transformation of each value in the list.
128143 /// </summary>
129144 /// <param name="values">The list of values to transform.</param>
130- /// <param name="lambda1">The transformation exponent. Range -5 to +5.</param>
131- /// <param name="lambda2">The transformation shift for negative values.</param>
132- public static List < double > Transform ( IList < double > values , double lambda1 , double lambda2 = 0.0d )
145+ /// <param name="lambda">The transformation exponent. Range -5 to +5.</param>
146+ public static List < double > Transform ( IList < double > values , double lambda )
133147 {
134148 var newValues = new List < double > ( ) ;
135149 for ( int i = 0 ; i < values . Count ; i ++ )
136- newValues . Add ( Transform ( values [ i ] , lambda1 , lambda2 ) ) ;
150+ newValues . Add ( Transform ( values [ i ] , lambda ) ) ;
137151 return newValues ;
138152 }
139153
140154 /// <summary>
141155 /// Returns the reverse of the Box-Cox transformed value.
142156 /// </summary>
143157 /// <param name="value">The value to reverse transform.</param>
144- /// <param name="lambda1">The transformation exponent. Range -5 to +5.</param>
145- /// <param name="lambda2">The transformation shift for negative values.</param>
146- public static double ReverseTransform ( double value , double lambda1 , double lambda2 = 0.0d )
158+ /// <param name="lambda">The transformation exponent. Range -5 to +5.</param>
159+ public static double InverseTransform ( double value , double lambda )
147160 {
148- if ( Math . Abs ( lambda1 ) > 5d ) return double . NaN ;
149- if ( lambda1 == 0d ) return Math . Exp ( value ) - lambda2 ;
150- return Math . Pow ( value * lambda1 + 1.0d , 1.0d / lambda1 ) - lambda2 ;
161+ if ( Math . Abs ( lambda ) > 5d ) return double . NaN ;
162+ if ( Math . Abs ( lambda ) < 1e-8 ) return Math . Exp ( value ) ;
163+ return Math . Pow ( value * lambda + 1.0d , 1.0d / lambda ) ;
151164 }
152165
153166 /// <summary>
154- /// Returns the reverse of each Box-Cox transformed value in the list.
167+ /// Returns the inverse of each Box-Cox transformed value in the list.
155168 /// </summary>
156169 /// <param name="values">The list of values to reverse transform.</param>
157- /// <param name="lambda1">The transformation exponent. Range -5 to +5.</param>
158- /// <param name="lambda2">The transformation shift for negative values.</param>
159- public static List < double > ReverseTransform ( IList < double > values , double lambda1 , double lambda2 = 0.0d )
170+ /// <param name="lambda">The transformation exponent. Range -5 to +5.</param>
171+ public static List < double > InverseTransform ( IList < double > values , double lambda )
160172 {
161173 var newValues = new List < double > ( ) ;
162174 for ( int i = 0 ; i < values . Count ; i ++ )
163- newValues . Add ( ReverseTransform ( values [ i ] , lambda1 , lambda2 ) ) ;
175+ newValues . Add ( InverseTransform ( values [ i ] , lambda ) ) ;
164176 return newValues ;
165177 }
166178 }
0 commit comments