# -*- coding: utf-8 -*-
# Copyright (C) 2008, Luís Pedro Coelho <lpc@cmu.edu>
# 
# Permission is hereby granted, free of charge, to any person obtaining a copy
#  of this software and associated documentation files (the "Software"), to deal
#  in the Software without restriction, including without limitation the rights
#  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#  copies of the Software, and to permit persons to whom the Software is
#  furnished to do so, subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be included in
#  all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
#  THE SOFTWARE.

from __future__ import division
import numpy
try:
    import ncreduce
    _std=ncreduce.std
except:
    _std=numpy.std

__all__ = ['zscore','zscore_normalise','interval_normalise','chkfinite','icdf_normalise']

def zscore(features):
    """
    features = zscore(features)

    Returns a copy of features which has been normalised to zscores 
    """
    mu=features.mean(0)
    sigma=_std(features,0)
    sigma[sigma == 0] = 1
    return (features - mu) / sigma

class subtract_divide(object):
    def __init__(self,features=None):
        if features:
            self.train(features)
    def __call__(self,features):
        return (features - self.mu)/self.factor

class zscore_normalise(subtract_divide):
    '''
    Normalise to z-scores

    A preprocessor that normalises features to z scores.
    '''
    def __init__(self,features=None):
        subtract_divide.__init__(self,features)

    def train(self,features,labels):
        self.mu=features.mean(0)
        self.factor=_std(features,0)
        self.factor[self.sigma == 0.]=1 # This makes the division a null op.

class interval_normalise(subtract_divide):
    '''
    Linearly scale to the interval [-1,1] (per libsvm recommendation)

    '''
    def __init__(self,features=None):
        subtract_divide.__init__(self,features)

    def train(self,features,labels):
        self.mu=features.mean(0)
        self.factor=features.max(0)-features.min(0)
        self.factor[self.range == 0.]=1 # This makes the division a null op.

class chkfinite(object):
    '''
    Fill NaN & Inf values

    Replaces NaN & Inf values with zeros.
    '''
    __slots__ = []
    def __init__(self):
        pass

    def train(self,features,labels):
        pass

    def apply(self,features):
        nans=isnan(features)+isinf(features)
        if nans.any():
            features=features.copy()
            features[nans]=0
        return features

# vim: set ts=4 sts=4 sw=4 expandtab smartindent:

@@ -1501,17 +1501,19 @@
 %22%0A    mu
-=
+ = 
 features
@@ -1530,17 +1530,19 @@
    sigma
-=
+ = 
 _std(fea
@@ -1800,18 +1800,21 @@
  - self.
-mu
+shift
 )/self.f
@@ -2090,35 +2090,40 @@
 ):%0A        self.
-mu=
+shift = 
 features.mean(0)
@@ -2142,17 +2142,19 @@
 f.factor
-=
+ = 
 _std(fea
@@ -2191,21 +2191,23 @@
 elf.
-sigma 
+factor
 == 0.%5D
-=
+ = 
 1 # 
@@ -2508,16 +2508,30 @@
     
-self.mu=
+D = features.max(0) - 
 feat
@@ -2528,34 +2528,33 @@
 (0) - features.m
-ea
+i
 n(0)%0A        sel
@@ -2555,23 +2555,24 @@
    self.
-factor=
+shift = 
 features
@@ -2577,29 +2577,48 @@
 es.m
-ax(0)-features.min(0)
+ean(0) + D/2.%0A        self.factor = D/2.
 %0A   
@@ -2643,14 +2643,14 @@
 elf.
-range 
+factor
 == 0

Fix interval_normalise (seemed so simple and, yet, I got it wrong)