Skip to content

Commit

Permalink
Part 1 of 2 - Making concrete math transformers (#255)
Browse files Browse the repository at this point in the history
  • Loading branch information
leahmcguire committed Mar 29, 2019
1 parent 32ec731 commit 9789468
Show file tree
Hide file tree
Showing 12 changed files with 665 additions and 99 deletions.
108 changes: 17 additions & 91 deletions core/src/main/scala/com/salesforce/op/dsl/RichNumericFeature.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,9 @@ package com.salesforce.op.dsl

import com.salesforce.op.features.FeatureLike
import com.salesforce.op.features.types._
import com.salesforce.op.stages.base.binary.BinaryLambdaTransformer
import com.salesforce.op.stages.base.unary.UnaryLambdaTransformer
import com.salesforce.op.stages.impl.feature._
import com.salesforce.op.stages.impl.preparators.{CorrelationType, CorrelationExclusion, SanityChecker}
import com.salesforce.op.stages.impl.preparators.{CorrelationExclusion, CorrelationType, SanityChecker}
import com.salesforce.op.stages.impl.regression.IsotonicRegressionCalibrator
import com.salesforce.op.utils.tuples.RichTuple._
import com.salesforce.op.utils.numeric.Number

import scala.language.postfixOps
import scala.reflect.ClassTag
Expand Down Expand Up @@ -71,22 +67,8 @@ trait RichNumericFeature {
* @tparam I2 that feature output type
* @return transformed feature
*/
def /[I2 <: OPNumeric[_] : TypeTag](that: FeatureLike[I2]): FeatureLike[Real] = {
f.transformWith[I2, Real](
stage = new BinaryLambdaTransformer[I, I2, Real](
operationName = "divide",
transformFn = (i1: I, i2: I2) => {
val result = for {
x <- i1.toDouble
y <- i2.toDouble
} yield x / y

result filter Number.isValid toReal
}
),
f = that
)
}
def /[I2 <: OPNumeric[_] : TypeTag](that: FeatureLike[I2]): FeatureLike[Real] =
f.transformWith(new DivideTransformer[I, I2](), that)

/**
* Apply Multiply transformer shortcut function
Expand All @@ -102,22 +84,8 @@ trait RichNumericFeature {
* @tparam I2 that feature output type
* @return transformed feature
*/
def *[I2 <: OPNumeric[_] : TypeTag](that: FeatureLike[I2]): FeatureLike[Real] = {
f.transformWith[I2, Real](
stage = new BinaryLambdaTransformer[I, I2, Real](
operationName = "multiply",
transformFn = (i1: I, i2: I2) => {
val result = for {
x <- i1.toDouble
y <- i2.toDouble
} yield x * y

result filter Number.isValid toReal
}
),
f = that
)
}
def *[I2 <: OPNumeric[_] : TypeTag](that: FeatureLike[I2]): FeatureLike[Real] =
f.transformWith(new MultiplyTransformer[I, I2](), that)

/**
* Apply Plus transformer shortcut function
Expand All @@ -133,15 +101,8 @@ trait RichNumericFeature {
* @tparam I2 that feature output type
* @return transformed feature
*/
def +[I2 <: OPNumeric[_] : TypeTag](that: FeatureLike[I2]): FeatureLike[Real] = {
f.transformWith[I2, Real](
stage = new BinaryLambdaTransformer[I, I2, Real](
operationName = "plus",
transformFn = (i1: I, i2: I2) => (i1.toDouble -> i2.toDouble).map(_ + _).toReal
),
f = that
)
}
def +[I2 <: OPNumeric[_] : TypeTag](that: FeatureLike[I2]): FeatureLike[Real] =
f.transformWith(new AddTransformer[I, I2](), that)

/**
* Apply Minus transformer shortcut function
Expand All @@ -157,23 +118,8 @@ trait RichNumericFeature {
* @tparam I2 that feature output type
* @return transformed feature
*/
def -[I2 <: OPNumeric[_] : TypeTag](that: FeatureLike[I2]): FeatureLike[Real] = {
f.transformWith[I2, Real](
stage = new BinaryLambdaTransformer[I, I2, Real](
operationName = "minus",
transformFn = (i1: I, i2: I2) => {
val optZ = (i1.toDouble, i2.toDouble) match {
case (Some(x), Some(y)) => Some(x - y)
case (Some(x), None) => Some(x)
case (None, Some(y)) => Some(-y)
case (None, None) => None
}
optZ.toReal
}
),
f = that
)
}
def -[I2 <: OPNumeric[_] : TypeTag](that: FeatureLike[I2]): FeatureLike[Real] =
f.transformWith(new SubtractTransformer[I, I2](), that)

/**
* Apply Divide scalar transformer shortcut function
Expand All @@ -183,13 +129,8 @@ trait RichNumericFeature {
* @tparam N value type
* @return transformed feature
*/
def /[N](v: N)(implicit n: Numeric[N]): FeatureLike[Real] = {
f.transformWith(
new UnaryLambdaTransformer[I, Real](
operationName = "divideS",
transformFn = r => r.toDouble.map(_ / n.toDouble(v)).filter(Number.isValid).toReal)
)
}
def /[N](v: N)(implicit n: Numeric[N]): FeatureLike[Real] =
f.transformWith(new ScalarDivideTransformer(scalar = v))

/**
* Apply Multiply scalar transformer shortcut function
Expand All @@ -199,13 +140,8 @@ trait RichNumericFeature {
* @tparam N value type
* @return transformed feature
*/
def *[N](v: N)(implicit n: Numeric[N]): FeatureLike[Real] = {
f.transformWith(
new UnaryLambdaTransformer[I, Real](
operationName = "multiplyS",
transformFn = r => r.toDouble.map(_ * n.toDouble(v)).filter(Number.isValid).toReal)
)
}
def *[N](v: N)(implicit n: Numeric[N]): FeatureLike[Real] =
f.transformWith(new ScalarMultiplyTransformer(scalar = v))

/**
* Apply Plus scalar transformer shortcut function
Expand All @@ -215,13 +151,8 @@ trait RichNumericFeature {
* @tparam N value type
* @return transformed feature
*/
def +[N](v: N)(implicit n: Numeric[N]): FeatureLike[Real] = {
f.transformWith(
new UnaryLambdaTransformer[I, Real](
operationName = "plusS",
transformFn = r => r.toDouble.map(_ + n.toDouble(v)).toReal)
)
}
def +[N](v: N)(implicit n: Numeric[N]): FeatureLike[Real] =
f.transformWith(new ScalarAddTransformer[I, N](scalar = v))

/**
* Apply Minus scalar transformer shortcut function
Expand All @@ -231,13 +162,8 @@ trait RichNumericFeature {
* @tparam N value type
* @return transformed feature
*/
def -[N](v: N)(implicit n: Numeric[N]): FeatureLike[Real] = {
f.transformWith(
new UnaryLambdaTransformer[I, Real](
operationName = "minusS",
transformFn = r => r.toDouble.map(_ - n.toDouble(v)).toReal)
)
}
def -[N](v: N)(implicit n: Numeric[N]): FeatureLike[Real] =
f.transformWith(new ScalarSubtractTransformer[I, N](scalar = v))

}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
/*
* Copyright (c) 2017, Salesforce.com, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package com.salesforce.op.stages.impl.feature

import com.salesforce.op.UID
import com.salesforce.op.features.types._
import com.salesforce.op.stages.base.binary.BinaryTransformer
import com.salesforce.op.stages.base.unary.UnaryTransformer
import com.salesforce.op.utils.numeric.Number
import com.salesforce.op.utils.tuples.RichTuple._

import scala.reflect.runtime.universe.TypeTag

/**
* Plus function truth table (Real as example):
*
* Real.empty + Real.empty = Real.empty
* Real.empty + Real(x) = Real(x)
* Real(x) + Real.empty = Real(x)
* Real(x) + Real(y) = Real(x + y)
*/
class AddTransformer[I1 <: OPNumeric[_], I2 <: OPNumeric[_]]
(
uid: String = UID[AddTransformer[_, _]]
)(
implicit override val tti1: TypeTag[I1],
override val tti2: TypeTag[I2]
) extends BinaryTransformer[I1, I2, Real](operationName = "plus", uid = uid){
override def transformFn: (I1, I2) => Real = (i1: I1, i2: I2) => (i1.toDouble -> i2.toDouble).map(_ + _).toReal
}

/**
* Scalar addition transformer
*
* @param scalar scalar value
* @param uid uid for instance
* @param tti type tag for input
* @param n value converter
* @tparam I input feature type
* @tparam N value type
*/
class ScalarAddTransformer[I <: OPNumeric[_], N]
(
val scalar: N,
uid: String = UID[ScalarAddTransformer[_, _]]
)(
implicit override val tti: TypeTag[I],
val n: Numeric[N]
) extends UnaryTransformer[I, Real](operationName = "plusS", uid = uid){
override def transformFn: I => Real = (i: I) => i.toDouble.map(_ + n.toDouble(scalar)).toReal
}


/**
* Minus function truth table (Real as example):
*
* Real.empty - Real.empty = Real.empty
* Real.empty - Real(x) = Real(-x)
* Real(x) - Real.empty = Real(x)
* Real(x) - Real(y) = Real(x - y)
*/
class SubtractTransformer[I1 <: OPNumeric[_], I2 <: OPNumeric[_]]
(
uid: String = UID[SubtractTransformer[_, _]]
)(
implicit override val tti1: TypeTag[I1],
override val tti2: TypeTag[I2]
) extends BinaryTransformer[I1, I2, Real](operationName = "minus", uid = uid){
override def transformFn: (I1, I2) => Real = (i1: I1, i2: I2) => {
val optZ = (i1.toDouble, i2.toDouble) match {
case (Some(x), Some(y)) => Some(x - y)
case (Some(x), None) => Some(x)
case (None, Some(y)) => Some(-y)
case (None, None) => None
}
optZ.toReal
}
}


/**
* Scalar subtract transformer
*
* @param scalar scalar value
* @param uid uid for instance
* @param tti type tag for input
* @param n value converter
* @tparam I input feature type
* @tparam N value type
*/
class ScalarSubtractTransformer[I <: OPNumeric[_], N]
(
val scalar: N,
uid: String = UID[ScalarSubtractTransformer[_, _]]
)(
implicit override val tti: TypeTag[I],
val n: Numeric[N]
) extends UnaryTransformer[I, Real](operationName = "minusS", uid = uid){
override def transformFn: I => Real = (i: I) => i.toDouble.map(_ - n.toDouble(scalar)).toReal
}

/**
* Multiply function truth table (Real as example):
*
* Real.empty * Real.empty = Real.empty
* Real.empty * Real(x) = Real.empty
* Real(x) * Real.empty = Real.empty
* Real(x) * Real(y) = Real(x * y) filter ("is not NaN or Infinity")
*/
class MultiplyTransformer[I1 <: OPNumeric[_], I2 <: OPNumeric[_]]
(
uid: String = UID[MultiplyTransformer[_, _]]
)(
implicit override val tti1: TypeTag[I1],
override val tti2: TypeTag[I2]
) extends BinaryTransformer[I1, I2, Real](operationName = "multiply", uid = uid){
override def transformFn: (I1, I2) => Real = (i1: I1, i2: I2) => {
val result = for {
x <- i1.toDouble
y <- i2.toDouble
} yield x * y

result filter Number.isValid toReal
}
}

/**
* Scalar multiply transformer
*
* @param scalar scalar value
* @param uid uid for instance
* @param tti type tag for input
* @param n value converter
* @tparam I input feature type
* @tparam N value type
*/
class ScalarMultiplyTransformer[I <: OPNumeric[_], N]
(
val scalar: N,
uid: String = UID[ScalarMultiplyTransformer[_, _]]
)(
implicit override val tti: TypeTag[I],
val n: Numeric[N]
) extends UnaryTransformer[I, Real](operationName = "multiplyS", uid = uid){
override def transformFn: I => Real = (i: I) => i.toDouble.map(_ * n.toDouble(scalar)).filter(Number.isValid).toReal
}


/**
* Divide function truth table (Real as example):
*
* Real.empty / Real.empty = Real.empty
* Real.empty / Real(x) = Real.empty
* Real(x) / Real.empty = Real.empty
* Real(x) / Real(y) = Real(x * y) filter ("is not NaN or Infinity")
*/
class DivideTransformer[I1 <: OPNumeric[_], I2 <: OPNumeric[_]]
(
uid: String = UID[MultiplyTransformer[_, _]]
)(
implicit override val tti1: TypeTag[I1],
override val tti2: TypeTag[I2]
) extends BinaryTransformer[I1, I2, Real](operationName = "divide", uid = uid){
override def transformFn: (I1, I2) => Real = (i1: I1, i2: I2) => {
val result = for {
x <- i1.toDouble
y <- i2.toDouble
} yield x / y

result filter Number.isValid toReal
}
}


/**
* Scalar divide transformer
*
* @param scalar scalar value
* @param uid uid for instance
* @param tti type tag for input
* @param n value converter
* @tparam I input feature type
* @tparam N value type
*/
class ScalarDivideTransformer[I <: OPNumeric[_], N]
(
val scalar: N,
uid: String = UID[ScalarDivideTransformer[_, _]]
)(
implicit override val tti: TypeTag[I],
val n: Numeric[N]
) extends UnaryTransformer[I, Real](operationName = "divideS", uid = uid){
override def transformFn: I => Real = (i: I) => i.toDouble.map(_ / n.toDouble(scalar)).filter(Number.isValid).toReal
}

Loading

0 comments on commit 9789468

Please sign in to comment.