Skip to content

Commit

Permalink
Implement the logarithm functions (#1566)
Browse files Browse the repository at this point in the history
Add functions `dt.log()` and `dt.log10()`. For example:
```
>>> import datatable as dt
>>> X = dt.Frame([1, 2, 3])
>>> X[:, dt.log(dt.f[0])]
           C0
---  --------
 0   0       
 1   0.693147
 2   1.09861 

[3 rows x 1 column]
```

Closes #1558
  • Loading branch information
st-pasha committed Jan 17, 2019
1 parent 2cc6e48 commit da3b827
Show file tree
Hide file tree
Showing 9 changed files with 113 additions and 13 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Expand Up @@ -63,6 +63,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
Leader) online learning algorithm on a data frame (#1389). The implementation
is multi-threaded and has high performance.

- Added functions `log` and `log10` for computing the natural and base-10
logarithms of a column (#1558).


### Fixed

Expand Down
6 changes: 5 additions & 1 deletion c/expr/base_expr.cc
Expand Up @@ -335,19 +335,23 @@ static void init_unops() {
unop_rules[id(unop::PLUS, st)] = st;
unop_rules[id(unop::ABS, st)] = st;
unop_rules[id(unop::EXP, st)] = flt64;
unop_rules[id(unop::LOGE, st)] = flt64;
unop_rules[id(unop::LOG10, st)] = flt64;
}
unop_rules[id(unop::MINUS, bool8)] = int8;
unop_rules[id(unop::PLUS, bool8)] = int8;
unop_rules[id(unop::ABS, bool8)] = int8;
unop_rules[id(unop::INVERT, bool8)] = bool8;

unop_names.resize(7);
unop_names.resize(1 + id(unop::LOG10));
unop_names[id(unop::ISNA)] = "isna";
unop_names[id(unop::MINUS)] = "-";
unop_names[id(unop::PLUS)] = "+";
unop_names[id(unop::INVERT)] = "~";
unop_names[id(unop::ABS)] = "abs";
unop_names[id(unop::EXP)] = "exp";
unop_names[id(unop::LOGE)] = "log";
unop_names[id(unop::LOG10)] = "log10";
}


Expand Down
2 changes: 2 additions & 0 deletions c/expr/base_expr.h
Expand Up @@ -65,6 +65,8 @@ enum class unop : size_t {
INVERT = 4,
ABS = 5,
EXP = 6,
LOGE = 7,
LOG10 = 8,
};


Expand Down
23 changes: 21 additions & 2 deletions c/expr/unaryop.cc
Expand Up @@ -18,6 +18,8 @@ enum OpCode {
Invert = 4,
Abs = 5,
Exp = 6,
LogE = 7,
Log10 = 8,
};


Expand Down Expand Up @@ -73,7 +75,21 @@ inline static T op_abs(T x) {

template <typename T>
inline static double op_exp(T x) {
return ISNA<T>(x) ? GETNA<double>() : exp(static_cast<double>(x));
return ISNA<T>(x) ? GETNA<double>() : std::exp(static_cast<double>(x));
}


template <typename T>
inline static double op_loge(T x) {
return ISNA<T>(x) || x < 0 ? GETNA<double>()
: std::log(static_cast<double>(x));
}


template <typename T>
inline static double op_log10(T x) {
return ISNA<T>(x) || x < 0 ? GETNA<double>()
: std::log10(static_cast<double>(x));
}


Expand Down Expand Up @@ -111,6 +127,8 @@ static mapperfn resolve1(int opcode) {
case Minus: return map_n<IT, IT, op_minus<IT>>;
case Abs: return map_n<IT, IT, op_abs<IT>>;
case Exp: return map_n<IT, double, op_exp<IT>>;
case LogE: return map_n<IT, double, op_loge<IT>>;
case Log10: return map_n<IT, double, op_log10<IT>>;
case Invert:
if (std::is_floating_point<IT>::value) return nullptr;
return map_n<IT, IT, Inverse<IT>::impl>;
Expand Down Expand Up @@ -157,7 +175,8 @@ Column* unaryop(int opcode, Column* arg)
res_type = SType::BOOL;
} else if (arg_type == SType::BOOL && opcode == OpCode::Minus) {
res_type = SType::INT8;
} else if (opcode == OpCode::Exp) {
} else if (opcode == OpCode::Exp || opcode == OpCode::LogE ||
opcode == OpCode::Log10) {
res_type = SType::FLOAT64;
}
void* params[2];
Expand Down
5 changes: 3 additions & 2 deletions datatable/__init__.py
Expand Up @@ -7,7 +7,8 @@
from .__version__ import version as __version__
from .dt_append import rbind, cbind
from .frame import Frame
from .expr import mean, min, max, sd, isna, sum, count, first, abs, exp
from .expr import (mean, min, max, sd, isna, sum, count, first, abs, exp,
log, log10)
from .fread import fread, GenericReader, FreadWarning
from .graph import f, g, join, by
from .lib._datatable import (
Expand All @@ -30,7 +31,7 @@
__all__ = ("__version__", "__git_revision__",
"Frame", "max", "mean", "min", "open", "sd", "sum", "count", "first",
"isna", "fread", "GenericReader", "save", "stype", "ltype", "f", "g",
"join", "by", "abs", "exp",
"join", "by", "abs", "exp", "log", "log10",
"TypeError", "ValueError", "DatatableWarning", "FreadWarning",
"DataTable", "options",
"bool8", "int8", "int16", "int32", "int64",
Expand Down
14 changes: 8 additions & 6 deletions datatable/expr/__init__.py
Expand Up @@ -11,7 +11,7 @@
from .binary_expr import BinaryOpExpr
from .cast_expr import CastExpr
from .column_expr import ColSelectorExpr, NewColumnExpr
from .exp_expr import exp
from .exp_expr import exp, log, log10
from .isna_expr import isna
from .literal_expr import LiteralExpr
from .mean_expr import MeanReducer, mean
Expand All @@ -23,20 +23,22 @@

__all__ = (
"abs",
"count",
"exp",
"f",
"first",
"isna",
"log",
"log10",
"max",
"mean",
"min",
"sd",
"sum",
"exp",
"count",
"first",
"isna",
"BinaryOpExpr",
"CastExpr",
"ColSelectorExpr",
"NewColumnExpr",
"f",
"BaseExpr",
"LiteralExpr",
"MeanReducer",
Expand Down
4 changes: 4 additions & 0 deletions datatable/expr/consts.py
Expand Up @@ -134,6 +134,8 @@ class CStats(Enum):
unary_ops_rules[("+", st)] = st
unary_ops_rules[("abs", st)] = st
unary_ops_rules[("exp", st)] = stype.float64
unary_ops_rules[("log", st)] = stype.float64
unary_ops_rules[("log10", st)] = stype.float64

# Synchronize with OpCode in c/expr/unaryop.cc
unary_op_codes = {
Expand All @@ -144,6 +146,8 @@ class CStats(Enum):
"!": 4, # same as '~'
"abs": 5,
"exp": 6,
"log": 7,
"log10": 8,
}


Expand Down
30 changes: 29 additions & 1 deletion datatable/expr/exp_expr.py
Expand Up @@ -20,7 +20,7 @@
from datatable.lib import core
import math

__all__ = ("exp",)
__all__ = ("exp", "log", "log10")


def exp(x):
Expand All @@ -35,3 +35,31 @@ def exp(x):
return math.exp(x)
except OverflowError:
return math.inf


def log(x):
if isinstance(x, BaseExpr):
return UnaryOpExpr("log", x)
if isinstance(x, core.Frame):
return x[:, {col: UnaryOpExpr("log", f[col])
for col in x.names}]
if x is None or x < 0:
return None
elif x == 0:
return -math.inf
else:
return math.log(x)


def log10(x):
if isinstance(x, BaseExpr):
return UnaryOpExpr("log10", x)
if isinstance(x, core.Frame):
return x[:, {col: UnaryOpExpr("log10", f[col])
for col in x.names}]
if x is None or x < 0:
return None
elif x == 0:
return -math.inf
else:
return math.log10(x)
39 changes: 38 additions & 1 deletion tests/test_dt_expr.py
Expand Up @@ -21,6 +21,7 @@
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
#-------------------------------------------------------------------------------
import math
import pytest
import random
import datatable as dt
Expand Down Expand Up @@ -331,7 +332,6 @@ def test_exp_srcs(src):

def test_exp_all_stypes():
from datatable import exp
import math
src = [[-127, -5, -1, 0, 2, 127],
[-32767, -299, -7, 32767, 12, -543],
[-2147483647, -1000, 3, -589, 2147483647, 0],
Expand All @@ -355,6 +355,43 @@ def test_exp_all_stypes():




#-------------------------------------------------------------------------------
# log(), log10()
#-------------------------------------------------------------------------------

@pytest.mark.parametrize("fn", ["log", "log10"])
def test_log(fn):
dtlog = getattr(dt, fn)
mathlog = getattr(math, fn)
assert dtlog(None) is None
assert dtlog(-1) is None
assert dtlog(0) == -math.inf
assert dtlog(1) == 0.0
assert dtlog(5) == mathlog(5)
assert dtlog(10) == mathlog(10)
assert dtlog(793.54) == mathlog(793.54)
assert dtlog(math.inf) == math.inf


@pytest.mark.parametrize("src", dt_int + dt_float)
@pytest.mark.parametrize("fn", ["log", "log10"])
def test_log_srcs(src, fn):
dtlog = getattr(dt, fn)
mathlog = getattr(math, fn)
dt0 = dt.Frame(src)
dt1 = dtlog(dt0)
dt1.internal.check()
assert all([st == stype.float64 for st in dt1.stypes])
pyans = [None if x is None or x < 0 else
-math.inf if x == 0 else
mathlog(x)
for x in src]
assert dt1.to_list()[0] == pyans




#-------------------------------------------------------------------------------
# type-cast
#-------------------------------------------------------------------------------
Expand Down

0 comments on commit da3b827

Please sign in to comment.