0.6.2-dev0
FORCES
FORtran lib for Comp. Env. Sys.
Loading...
Searching...
No Matches
mo_boxcox.f90
Go to the documentation of this file.
1!> \file mo_boxcox.f90
2!> \brief \copybrief mo_boxcox
3!> \details \copydetails mo_boxcox
4
5!> \brief Box-Cox transformation of data.
6!> \details This module contains routines to calculate the Box-Cox transformation
7!! as well as estimating the best exponent for the Box-Cox transformation
8!> \changelog
9!! - March 2011, Matthias Cuntz
10!! - modified Python code of Travis Oliphant (2002): boxcox, llf_boxcox
11!! - Dec 2019: Robert Schweppe:
12!! - removed NR code (get_boxcox)
13!> \author Mathias Cuntz
14!> \date Aug 2011
15!> \copyright Copyright 2005-\today, the CHS Developers, Sabine Attinger: All rights reserved.
16!! FORCES is released under the LGPLv3+ license \license_note
18
19 USE mo_kind, ONLY : sp, dp
20 USE mo_utils, only : le
21
22 IMPLICIT NONE
23
24 PUBLIC :: boxcox
25 PUBLIC :: invboxcox
26
27 ! ------------------------------------------------------------------
28
29 !> \brief Transform a positive dataset with a Box-Cox power transformation.
30
31 !> \details Calculate Box-Cox power transformed values given the original values and the exponent lambda.\n
32 !! \f[ w_\text{Box-Cox}(x) =
33 !! \begin{cases}
34 !! \frac{x^\lambda - 1}{\lambda}&\text{, if }\lambda \neq 0 \\
35 !! \ln{x}&\text{, if }\lambda = 0
36 !! \end{cases} \f]
37 !! If an optional mask is given, then the Box-Cox transformation is only performed on
38 !! those locations that correspond to true values in the mask.\n
39 !! \f$x\f$ can be single or double precision. The result will have the same numerical precision.
40 !! \f$x\f$ can be scalar or vector.\n
41 !!
42 !! \b Example
43 !!
44 !! \code{.f90}
45 !! out = boxcox(x, lmbda, mask=mask)
46 !! \endcode
47 !!
48 !! See also test folder for a detailed example, "pf_tests/test_mo_boxcox".
49
50
51 !> \param[in] "real(sp/dp) :: x" Scalar/1D-array with input numbers (`>0.`)
52 !> \param[in] "real(sp/dp) :: lmbda" Exponent power of Box-Cox transform (`>= 0.`)
53 !> \param[in] "logical, optional :: mask" Scalar/1D-array of logical values with `size(x)`.
54 !! If present, only those locations in vec corresponding
55 !! to the true values in mask are used.
56 !> \retval "real(sp/dp) :: boxcox" Power transformed values (at `mask=.true.`)
57
58 !> \note Input values must be positive, i.e. \f$x > 0\f$.
59
60 !> \author Matthias Cuntz
61 !> \date March 2011
62 !! - Modified Python code of Travis Oliphant (2002): boxcox, llf_boxcox, get_boxcox
63 !! - Modified numerical recipes: brent, mnbrak, swap, shft
64 !> \date Dec 2021
65 !! - Updated doxygen docs
66 INTERFACE boxcox
67 MODULE PROCEDURE boxcox_sp, boxcox_dp
68 END INTERFACE boxcox
69
70 ! ------------------------------------------------------------------
71
72 !> \brief Back-transformation of Box-Cox-transformed data.
73
74 !> \details Calculate the inverse Box-Cox given the transformed values and the exponent lambda.
75 !! \f[ w_\text{Box-Cox}^{-1}(y) =
76 !! \begin{cases}
77 !! (\lambda y + 1)^{\frac{1}{\lambda}}&\text{, if }\lambda \neq 0 \\
78 !! e^{y}&\text{, if }\lambda = 0
79 !! \end{cases} \f]
80 !! If an optional mask is given, then the inverse Box-Cox transformation is only performed on
81 !! those locations that correspond to true values in the mask.\n
82 !! \f$y\f$ can be single or double precision. The result will have the same numerical precision.\n
83 !! \f$y\f$ can be scalar or vector.
84 !!
85 !! \b Example
86 !!
87 !! \code{.f90}
88 !! out = invboxcox(x, lmbda, mask=mask)
89 !! \endcode
90 !!
91 !! See also test folder for a detailed example, "pf_tests/test_mo_boxcox".
92
93 !> \param[in] "real(sp/dp) :: y" Scalar/1D-array with input numbers (`>0.`)
94 !> \param[in] "real(sp/dp) :: lmbda" Exponent power of Box-Cox transform (`>= 0.`)
95 !> \param[in] "optional, logical :: mask" 1D-array of logical values with `size(x)`.
96 !! If present, only those locations in vec corresponding to the
97 !! true values in mask are used.
98 !! Only applicable if `x` is a 1D-array.
99 !> \retval "real(sp/dp) :: invboxcox" Back transformed values (at `mask=.true.`)
100
101 !> \author Matthias Cuntz
102 !> \author Juliane Mai
103 !> \author Sebastian Müller
104 !> \date March 2011
105 !! - Modified MC: Python code of Travis Oliphant (2002): boxcox, llf_boxcox, get_boxcox
106 !! - Modified MC: numerical recipes: brent, mnbrak, swap, shft
107 !! - Modified JM: scalar version of invboxcox
108 INTERFACE invboxcox
109 MODULE PROCEDURE invboxcox_0d_sp, invboxcox_0d_dp, invboxcox_1d_sp, invboxcox_1d_dp
110 END INTERFACE invboxcox
111
112 ! ------------------------------------------------------------------
113
114 PRIVATE
115
116 ! ------------------------------------------------------------------
117
118CONTAINS
119
120 ! ------------------------------------------------------------------
121
122 FUNCTION boxcox_sp(x, lmbda, mask)
123
124 IMPLICIT NONE
125
126 REAL(sp), DIMENSION(:), INTENT(in) :: x
127 REAL(sp), INTENT(in) :: lmbda
128 LOGICAL, DIMENSION(:), INTENT(in), OPTIONAL :: mask
129 REAL(sp), DIMENSION(size(x)) :: boxcox_sp
130
131 LOGICAL, DIMENSION(size(x)) :: maske
132 REAL(sp) :: lmbda1
133
134 maske(:) = .true.
135 if (present(mask)) then
136 if (size(mask) /= size(x)) stop 'Error boxcox_sp: size(mask) /= size(x)'
137 maske = mask
138 endif
139 if (any((le(x, 0.0_sp)) .and. maske)) stop 'Error boxcox_sp: x <= 0'
140 if (abs(lmbda) < tiny(0.0_sp)) then
141 where (maske)
142 boxcox_sp = log(x)
143 elsewhere
144 boxcox_sp = x
145 end where
146 else
147 lmbda1 = 1.0_sp / lmbda
148 boxcox_sp = merge((exp(lmbda * log(x)) - 1.0_sp) * lmbda1, x, maske)
149 endif
150
151 END FUNCTION boxcox_sp
152
153
154 FUNCTION boxcox_dp(x, lmbda, mask)
155
156 IMPLICIT NONE
157
158 REAL(dp), DIMENSION(:), INTENT(in) :: x
159 REAL(dp), INTENT(in) :: lmbda
160 LOGICAL, DIMENSION(:), INTENT(in), OPTIONAL :: mask
161 REAL(dp), DIMENSION(size(x)) :: boxcox_dp
162
163 LOGICAL, DIMENSION(size(x)) :: maske
164 REAL(dp) :: lmbda1
165
166 maske(:) = .true.
167 if (present(mask)) then
168 if (size(mask) /= size(x)) stop 'Error boxcox_dp: size(mask) /= size(x)'
169 maske = mask
170 endif
171 if (any((le(x, 0.0_dp)) .and. maske)) then
172 stop 'Error boxcox_dp: x <= 0'
173 end if
174 if (abs(lmbda) < tiny(0.0_dp)) then
175 where (maske)
176 boxcox_dp = log(x)
177 elsewhere
178 boxcox_dp = x
179 end where
180 else
181 lmbda1 = 1.0_dp / lmbda
182 boxcox_dp = merge((exp(lmbda * log(x)) - 1.0_dp) * lmbda1, x, maske)
183 endif
184
185 END FUNCTION boxcox_dp
186
187 ! ------------------------------------------------------------------
188
189 FUNCTION invboxcox_1d_sp(x, lmbda, mask)
190
191 IMPLICIT NONE
192
193 REAL(sp), DIMENSION(:), INTENT(in) :: x
194 REAL(sp), INTENT(in) :: lmbda
195 LOGICAL, DIMENSION(:), INTENT(in), OPTIONAL :: mask
196 REAL(sp), DIMENSION(size(x)) :: invboxcox_1d_sp
197
198 LOGICAL, DIMENSION(size(x)) :: maske
199 REAL(sp) :: lmbda1
200 REAL(sp), DIMENSION(size(x)) :: temp
201
202 maske(:) = .true.
203 if (present(mask)) then
204 if (size(mask) /= size(x)) stop 'Error invboxcox_1d_sp: size(mask) /= size(x)'
205 maske = mask
206 endif
207 if (abs(lmbda) < tiny(0.0_sp)) then
208 where (maske)
209 invboxcox_1d_sp = exp(x)
210 elsewhere
211 invboxcox_1d_sp = x
212 end where
213 else
214 lmbda1 = 1.0_sp / lmbda
215 temp = lmbda * x + 1.0_sp
216 where (temp > 0.0_sp)
217 temp = exp(lmbda1 * log(temp))
218 elsewhere
219 temp = 0.0_sp
220 end where
221 invboxcox_1d_sp = merge(temp, x, maske)
222 endif
223
224 END FUNCTION invboxcox_1d_sp
225
226 FUNCTION invboxcox_1d_dp(x, lmbda, mask)
227
228 IMPLICIT NONE
229
230 REAL(dp), DIMENSION(:), INTENT(in) :: x
231 REAL(dp), INTENT(in) :: lmbda
232 LOGICAL, DIMENSION(:), INTENT(in), OPTIONAL :: mask
233 REAL(dp), DIMENSION(size(x)) :: invboxcox_1d_dp
234
235 LOGICAL, DIMENSION(size(x)) :: maske
236 REAL(dp) :: lmbda1
237 REAL(dp), DIMENSION(size(x)) :: temp
238
239 maske(:) = .true.
240 if (present(mask)) then
241 if (size(mask) /= size(x)) stop 'Error invboxcox_1d_dp: size(mask) /= size(x)'
242 maske = mask
243 endif
244 if (abs(lmbda) < tiny(0.0_dp)) then
245 where (maske)
246 invboxcox_1d_dp = exp(x)
247 elsewhere
248 invboxcox_1d_dp = x
249 end where
250 else
251 lmbda1 = 1.0_dp / lmbda
252 temp = lmbda * x + 1.0_dp
253 where (temp > 0.0_dp)
254 temp = exp(lmbda1 * log(temp))
255 elsewhere
256 temp = 0.0_dp
257 end where
258 invboxcox_1d_dp = merge(temp, x, maske)
259 endif
260
261 END FUNCTION invboxcox_1d_dp
262
263 FUNCTION invboxcox_0d_sp(x, lmbda)
264
265 IMPLICIT NONE
266
267 REAL(sp), INTENT(in) :: x
268 REAL(sp), INTENT(in) :: lmbda
269 REAL(sp) :: invboxcox_0d_sp
270
271 REAL(sp) :: lmbda1
272 REAL(sp) :: temp
273
274 if (abs(lmbda) < tiny(0.0_sp)) then
275 invboxcox_0d_sp = exp(x)
276 else
277 lmbda1 = 1.0_sp / lmbda
278 temp = lmbda * x + 1.0_sp
279 if (temp > 0.0_sp) then
280 temp = exp(lmbda1 * log(temp))
281 else
282 temp = 0.0_sp
283 end if
284 invboxcox_0d_sp = temp
285 endif
286
287 END FUNCTION invboxcox_0d_sp
288
289 FUNCTION invboxcox_0d_dp(x, lmbda)
290
291 IMPLICIT NONE
292
293 REAL(dp), INTENT(in) :: x
294 REAL(dp), INTENT(in) :: lmbda
295 REAL(dp) :: invboxcox_0d_dp
296
297 REAL(dp) :: lmbda1
298 REAL(dp) :: temp
299
300 if (abs(lmbda) < tiny(0.0_dp)) then
301 invboxcox_0d_dp = exp(x)
302 else
303 lmbda1 = 1.0_dp / lmbda
304 temp = lmbda * x + 1.0_dp
305 if (temp > 0.0_dp) then
306 temp = exp(lmbda1 * log(temp))
307 else
308 temp = 0.0_dp
309 end if
310 invboxcox_0d_dp = temp
311 endif
312
313 END FUNCTION invboxcox_0d_dp
314
315END MODULE mo_boxcox
Transform a positive dataset with a Box-Cox power transformation.
Definition mo_boxcox.f90:66
Back-transformation of Box-Cox-transformed data.
Comparison of real values: a <= b.
Definition mo_utils.F90:299
Box-Cox transformation of data.
Definition mo_boxcox.f90:17
Define number representations.
Definition mo_kind.F90:17
integer, parameter sp
Single Precision Real Kind.
Definition mo_kind.F90:44
integer, parameter dp
Double Precision Real Kind.
Definition mo_kind.F90:46
General utilities for the CHS library.
Definition mo_utils.F90:20