Python库:Yeo-Johnson转换

Python库:Yeo-Johnson转换,python,r,scipy,transform,r-caret,Python,R,Scipy,Transform,R Caret,我正在寻找Python中的实现。不幸的是,SciPy库实现了box-cox(仅适用于正数) 目前,我正在通过RPY2使用一个R包();然而,它在性能上有很大的影响 谢谢 附言:从Python调用插入符号::预处理: #!/usr/bin/env python from rpy2.robjects.functions import SignatureTranslatedFunction import rpy2.robjects as robjects from rpy2.robjects.pack

我正在寻找Python中的实现。不幸的是,SciPy库实现了box-cox(仅适用于正数)

目前,我正在通过RPY2使用一个R包();然而,它在性能上有很大的影响

谢谢

附言:从Python调用插入符号::预处理:

#!/usr/bin/env python
from rpy2.robjects.functions import SignatureTranslatedFunction
import rpy2.robjects as robjects
from rpy2.robjects.packages import importr

r_caret = importr("caret")
r_stats = importr("stats")

feature_in = list(range(-5, 5))

r_caret.preProcess = SignatureTranslatedFunction(r_caret.preProcess, {'r_lambda': 'lambda'})
r_scale = r_caret.preProcess(robjects.DataFrame({'a': robjects.FloatVector(feature_in)}),
                          method=robjects.StrVector(["center", "scale", "YeoJohnson"]),
                          r_lambda=robjects.IntVector([-10, 10, 0.1]),
                          na_remove=False)
temp = r_stats.predict(r_scale, robjects.DataFrame({'a': robjects.FloatVector(feature_in)}))
feature_out = np.array(temp.rx2(1), dtype=np.float64)

print(feature_in)  # output [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4]
print(feature_out)  # output [-1.46625361 -1.14751589 -0.82725836 -0.50508336 -0.1803113  0.14850228  0.48337191  0.8224039  1.16416754  1.5079769]
解决方案如下:

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import warnings
import numpy as np
import pandas as pd
import sys

__author__ = "Mohsen Mesgarpour"
__copyright__ = "Copyright 2016, https://github.com/mesgarpour"
__credits__ = ["Mohsen Mesgarpour"]
__license__ = "GPL"
__version__ = "1.0"
__maintainer__ = "Mohsen Mesgarpour"
__email__ = "mohsen.mesgarpour@gmail.com"


class YeoJohnson:
    """
    Computing Yeo-Johnson transofrmation, which is an extension of Box-Cox transformation
    but can handle both positive and negative values.

    References:
    Weisberg, S. (2001). Yeo-Johnson Power Transformations.
    Department of Applied Statistics, University of Minnesota. Retrieved June, 1, 2003.
    https://www.stat.umn.edu/arc/yjpower.pdf

    Adapted from CRAN - Package VGAM
    """
    def fit(self, y, lmbda, derivative=0, epsilon=np.finfo(np.float).eps, inverse=False):
        """
        :param y: The variable to be transformed (numeric array).
        :param lmbda: The function's Lambda value (numeric value or array).
        :param derivative: The derivative with respect to lambda
        (non-negative integer; default: ordinary function evaluation).
        :param epsilon: The lambda's tolerance (positive value).
        :param inverse: The inverse transformation option (logical value).
        :return: The Yeo-Johnson transformation or its inverse, or its derivatives with respect to lambda, of y.
        """
        # Validate arguments
        self.__validate(y, lmbda, derivative, epsilon, inverse)

        # initialise
        y = np.array(y, dtype=float)
        result = y
        if not (isinstance(lmbda, list) or isinstance(lmbda, np.ndarray)):
            lmbda, y = np.broadcast_arrays(lmbda, y)
            lmbda = np.array(lmbda, dtype=float)
        l0 = np.abs(lmbda) > epsilon
        l2 = np.abs(lmbda - 2) > epsilon

        # Inverse
        with warnings.catch_warnings():  # suppress warnings
            warnings.simplefilter("ignore")
            if inverse is True:
                mask = np.where(((y >= 0) & l0) == True)
                result[mask] = np.power(np.multiply(y[mask], lmbda[mask]) + 1, 1 / lmbda[mask]) - 1

                mask = np.where(((y >= 0) & ~l0) == True)
                result[mask] = np.expm1(y[mask])

                mask = np.where(((y < 0) & l2) == True)
                result[mask] = 1 - np.power(np.multiply(-(2 - lmbda[mask]), y[mask]) + 1, 1 / (2 - lmbda[mask]))

                mask = np.where(((y < 0) & ~l2) == True)
                result[mask] = -np.expm1(-y[mask])

            # Derivative
            else:
                if derivative == 0:
                    mask = np.where(((y >= 0) & l0) == True)
                    result[mask] = np.divide(np.power(y[mask] + 1, lmbda[mask]) - 1, lmbda[mask])

                    mask = np.where(((y >= 0) & ~l0) == True)
                    result[mask] = np.log1p(y[mask])

                    mask = np.where(((y < 0) & l2) == True)
                    result[mask] = np.divide(-(np.power(-y[mask] + 1, 2 - lmbda[mask]) - 1), 2 - lmbda[mask])

                    mask = np.where(((y < 0) & ~l2) == True)
                    result[mask] = -np.log1p(-y[mask])

                # Not Derivative
                else:
                    p = self.fit(y, lmbda, derivative=derivative - 1, epsilon=epsilon, inverse=inverse)

                    mask = np.where(((y >= 0) & l0) == True)
                    result[mask] = np.divide(np.multiply(np.power(y[mask] + 1, lmbda[mask]),
                                                         np.power(np.log1p(y[mask]), derivative)) -
                                             np.multiply(derivative, p[mask]), lmbda[mask])

                    mask = np.where(((y >= 0) & ~l0) == True)
                    result[mask] = np.divide(np.power(np.log1p(y[mask]), derivative + 1), derivative + 1)

                    mask = np.where(((y < 0) & l2) == True)
                    result[mask] = np.divide(-(np.multiply(np.power(-y[mask] + 1, 2 - lmbda[mask]),
                                                                    np.power(-np.log1p(-y[mask]), derivative)) -
                                                        np.multiply(derivative, p[mask])), 2 - lmbda[mask])

                    mask = np.where(((y < 0) & ~l2) == True)
                    result[mask] = np.divide(np.power(-np.log1p(-y[mask]), derivative + 1), derivative + 1)

        return result

    @staticmethod
    def __validate(y, lmbda, derivative, epsilon, inverse):
        try:
            if not isinstance(y, (list, np.ndarray, pd.Series)):
                raise Exception("Argument 'y' must be a list!")
            if not isinstance(lmbda, (int, float, np.int, np.float)):
                if not isinstance(lmbda, (list, np.ndarray, pd.Series)) or len(lmbda) != len(y):
                    raise Exception("Argument 'lmbda' must be a number "
                                    "or a list, which its length matches 'y' argument!")
            if not isinstance(derivative, (int, float, np.int, np.float)) or derivative < 0:
                raise Exception("Argument 'derivative' must be a non-negative integer!")
            if not isinstance(epsilon, (int, float, np.int, np.float)) or epsilon <= 0:
                raise Exception("Argument 'epsilon' must be a positive number!")
            if not isinstance(inverse, bool):
                raise Exception("Argument 'inverse' must be boolean!")
            if inverse is True and derivative != 0:
                raise Exception("Argument 'derivative' must be zero "
                                "when argument 'inverse' is 'True'!")
        except ():
            sys.exit()
#/usr/bin/env python
#-*-编码:UTF-8-*-
进口警告
将numpy作为np导入
作为pd进口熊猫
导入系统
__作者=“Mohsen Mesgarpour”
__版权所有\=“版权所有2016,https://github.com/mesgarpour"
__学分u=[“Mohsen Mesgarpour”]
__许可证\=“GPL”
__版本=“1.0”
__维护人员\=“Mohsen Mesgarpour”
__发电子邮件给莫森。mesgarpour@gmail.com"
YeoJohnson类:
"""
计算Yeo-Johnson变换,这是Box-Cox变换的一个扩展
但是可以处理正值和负值。
参考资料:
Weisberg,S.(2001)。Yeo-Johnson电力转换。
明尼苏达大学应用统计系,检索于六月,1, 2003。
https://www.stat.umn.edu/arc/yjpower.pdf
改编自CRAN-Package VGAM
"""
def拟合(self,y,lmbda,导数=0,epsilon=np.finfo(np.float).eps,逆=False):
"""
:param y:要转换的变量(数字数组)。
:param lmbda:函数的Lambda值(数值或数组)。
:参数导数:相对于λ的导数
(非负整数;默认值:普通函数求值)。
:param epsilon:λ公差(正值)。
:param inverse:逆变换选项(逻辑值)。
:return:y的Yeo-Johnson变换或其逆,或其相对于λ的导数。
"""
#验证参数
自我验证(y,lmbda,导数,ε,逆)
#初始化
y=np.array(y,dtype=float)
结果=y
如果不是(isinstance(lmbda,列表)或isinstance(lmbda,np.ndarray)):
lmbda,y=np.广播_数组(lmbda,y)
lmbda=np.array(lmbda,dtype=float)
l0=np.abs(lmbda)>ε
l2=np.abs(lmbda-2)>ε
#反向
带有警告。捕获警告():#抑制警告
警告。simplefilter(“忽略”)
如果反向为真:
掩码=np。其中((y>=0)和l0)=True)
结果[mask]=np.幂(np.乘(y[mask],lmbda[mask])+1,1/lmbda[mask])-1
掩码=np。其中((y>=0)和~l0)==True)
结果[mask]=np.expm1(y[mask])
掩码=np。其中((y<0)和l2)=真)
结果[mask]=1-np.幂(np.乘(-(2-lmbda[mask]),y[mask])+1,1/(2-lmbda[mask]))
掩码=np。其中((y<0)和~l2)=真)
结果[掩码]=-np.expm1(-y[掩码])
#衍生工具
其他:
如果导数==0:
掩码=np。其中((y>=0)和l0)=True)
结果[mask]=np.divide(np.power(y[mask]+1,lmbda[mask])-1,lmbda[mask])
掩码=np。其中((y>=0)和~l0)==True)
结果[mask]=np.log1p(y[mask])
掩码=np。其中((y<0)和l2)=真)
结果[mask]=np.divide(-(np.power(-y[mask]+1,2-lmbda[mask])-1,2-lmbda[mask])
掩码=np。其中((y<0)和~l2)=真)
结果[掩码]=-np.log1p(-y[掩码])
#非衍生产品
其他:
p=自拟合(y,lmbda,导数=导数-1,ε=ε,逆=逆)
掩码=np。其中((y>=0)和l0)=True)
结果[mask]=np.除(np.乘(np.幂)(y[mask]+1,lmbda[mask]),
np.power(np.log1p(y[mask]),导数)-
np.乘法(导数,p[mask]),lmbda[mask])
掩码=np。其中((y>=0)和~l0)==True)
结果[mask]=np.除法(np.幂(np.log1p(y[mask]),导数+1,导数+1)
掩码=np。其中((y<0)和l2)=真)
结果[mask]=np.divide(-(np.multiply(np.power)(-y[mask]+1,2-lmbda[mask]),
np.power(-np.log1p(-y[mask]),导数)-
np.乘法(导数,p[掩码]),2-lmbda[掩码])
掩码=np。其中((y<0)和~l2)=真)
结果[mask]=np.除法(np.幂(-np.log1p(-y[mask]),导数+1,导数+1)
返回结果
@静力学方法
定义验证(y,lmbda,导数,ε,逆):
尝试:
如果不存在(y,(列表,np.ndarray,pd.Series)):
引发异常(“参数“y”必须是列表!”)
如果不存在(lmbda,(int,float,np.int,np.float)):
如果不是INSTANCE(lmbda,(列表,np.ndarray,pd.Series))或len(lmbda)!=len(y):
引发异常(“参数'lmbda'必须是数字”
或列表,其长度与'y'参数匹配!)
如果不是持续(导数,(int,float,np.int,np.float))或导数<0:
引发异常(“参数“导数”必须是非负整数!”)

如果不是恒定(epsilon,(int,float,np.int,np.float))或epsilon这些东西都可以用numpy直接DIY:

In [2]: def yj(lam, y):
    lam, y = np.broadcast_arrays(lam, y)
    l0 = lam == 0
    l2 = lam == 2
    result = np.empty_like(lam, dtype=float)

    mask1 = (~l0) & (y >= 0)
    ym, lm = y[mask1], lam[mask1]
    result[mask1] = ((ym + 1.)**lm - 1.) / lm

    mask2 = (l0) & (y >= 0)
    result[mask2] = np.log1p(y[mask2])

    # TODO: handle two more cases here, this is similar

    return result

请注意,以上是一个未经测试的演示,它不检查数据类型,也不关心边缘情况(小y、小lambda)等。

如果您从VGAM vs caret调用,是否会得到更快的结果(我可以想象,但值得一试)-无法找到pyt的任何结果