Repository: sintel-dev/sigllm (language: python)

Issue:
Refactor Transformation Primitives
Transformation primitives

* `Float2Scalar`
    * turn floats into scalar integers.
    * this includes rescaling and taking n decimal points.
* `Scalar2Float`
    * turns scalars into floats.
    * reverse operation of Float2Scalar
* `format_as_string`
    * convert windows of integers to windows of string
* `format_as_integer`
    * convert windows of strings into windows of integer


Discussion / hints:

Fix patch:
diff --git a/sigllm/primitives/jsons/sigllm.primitives.transformation.Float2Scalar.json b/sigllm/primitives/jsons/sigllm.primitives.transformation.Float2Scalar.json
new file mode 100644
index 0000000..b608362
--- /dev/null
+++ b/sigllm/primitives/jsons/sigllm.primitives.transformation.Float2Scalar.json
@@ -0,0 +1,54 @@
+{
+    "name": "sigllm.primitives.transformation.Float2Scaler",
+    "contributors": [
+        "Sarah Alnegheimish <smish@mit.edu>",
+        "Linh Nguyen <linhnk@mit.edu>"
+    ],
+    "description": "Transform float values into scalar.",
+    "classifiers": {
+        "type": "preprocessor",
+        "subtype": "transformer"
+    },
+    "modalities": [],
+    "primitive": "sigllm.primitives.transformation.Float2Scaler",
+    "fit": {
+        "method": "fit",
+        "args": [
+            {
+                "name": "X",
+                "type": "ndarray"
+            }
+        ]
+    },
+    "produce": {
+        "method": "transform",
+        "args": [
+            {
+                "name": "X",
+                "type": "ndarray"
+            }
+        ],
+        "output": [
+            {
+                "name": "X",
+                "type": "ndarray"
+            },
+            {
+                "name": "minimum",
+                "type": "float"
+            }
+        ]
+    },
+    "hyperparameters": {
+        "fixed": {
+            "decimal": {
+                "type": "int",
+                "default": 2
+            },
+            "rescale": {
+                "type": "bool",
+                "default": true
+            }
+        }
+    }
+}
diff --git a/sigllm/primitives/jsons/sigllm.primitives.transformation.Scalar2Float.json b/sigllm/primitives/jsons/sigllm.primitives.transformation.Scalar2Float.json
new file mode 100644
index 0000000..58bf8ab
--- /dev/null
+++ b/sigllm/primitives/jsons/sigllm.primitives.transformation.Scalar2Float.json
@@ -0,0 +1,42 @@
+{
+    "name": "sigllm.primitives.transformation.Scaler2Float",
+    "contributors": [
+        "Sarah Alnegheimish <smish@mit.edu>",
+        "Linh Nguyen <linhnk@mit.edu>"
+    ],
+    "description": "Transform scalar values to float.",
+    "classifiers": {
+        "type": "preprocessor",
+        "subtype": "transformer"
+    },
+    "modalities": [],
+    "primitive": "sigllm.primitives.transformation.Scaler2Float",
+    "produce": {
+        "method": "transform",
+        "args": [
+            {
+                "name": "X",
+                "type": "ndarray"
+            },
+            {
+                "name": "minimum",
+                "type": "float",
+                "default": 0
+            }
+        ],
+        "output": [
+            {
+                "name": "X",
+                "type": "ndarray"
+            }
+        ]
+    },
+    "hyperparameters": {
+        "fixed": {
+            "decimal": {
+                "type": "int",
+                "default": 2
+            }
+        }
+    }
+}
diff --git a/sigllm/primitives/jsons/sigllm.primitives.transformation.format_as_integer.json b/sigllm/primitives/jsons/sigllm.primitives.transformation.format_as_integer.json
new file mode 100644
index 0000000..48637aa
--- /dev/null
+++ b/sigllm/primitives/jsons/sigllm.primitives.transformation.format_as_integer.json
@@ -0,0 +1,46 @@
+{
+    "name": "sigllm.primitives.transformation.format_as_integer",
+    "contributors": [
+        "Sarah Alnegheimish <smish@mit.edu>",
+        "Linh Nguyen <linhnk@mit.edu>"
+    ],
+    "description": "Transform an ndarray of scalar values to an ndarray of string.",
+    "classifiers": {
+        "type": "preprocessor",
+        "subtype": "transformer"
+    },
+    "modalities": [],
+    "primitive": "sigllm.primitives.transformation.format_as_integer",
+    "produce": {
+        "method": "format_as_integer",
+        "args": [
+            {
+                "name": "X",
+                "type": "ndarray"
+            }
+        ],
+        "output": [
+            {
+                "name": "X",
+                "type": "ndarray"
+            }
+        ]
+    },
+    "hyperparameters": {
+        "fixed": {
+            "sep": {
+                "type": "string",
+                "default": ","
+            },
+            "trunc": {
+                "type": "int",
+                "default": null
+            },
+            "errors": {
+                "type": "string",
+                "default": "ignore"
+            }
+        }
+    }
+}
+
diff --git a/sigllm/primitives/jsons/sigllm.primitives.transformation.format_as_string.json b/sigllm/primitives/jsons/sigllm.primitives.transformation.format_as_string.json
new file mode 100644
index 0000000..bedb6ff
--- /dev/null
+++ b/sigllm/primitives/jsons/sigllm.primitives.transformation.format_as_string.json
@@ -0,0 +1,42 @@
+{
+    "name": "sigllm.primitives.transformation.format_as_string",
+    "contributors": [
+        "Sarah Alnegheimish <smish@mit.edu>",
+        "Linh Nguyen <linhnk@mit.edu>"
+    ],
+    "description": "Transform an ndarray of scalar values to an ndarray of string.",
+    "classifiers": {
+        "type": "preprocessor",
+        "subtype": "tranformer"
+    },
+    "modalities": [],
+    "primitive": "sigllm.primitives.transformation.format_as_string",
+    "produce": {
+        "method": "format_as_string",
+        "args": [
+            {
+                "name": "X",
+                "type": "ndarray"
+            }
+        ],
+        "output": [
+            {
+                "name": "X",
+                "type": "ndarray"
+            }
+        ]
+    },
+    "hyperparameters": {
+        "fixed": {
+            "sep": {
+                "type": "string",
+                "default": ","
+            },
+            "space": {
+                "type": "bool",
+                "default": false
+            }
+        }
+    }
+}
+
diff --git a/sigllm/primitives/preprocessing.py b/sigllm/primitives/preprocessing.py
deleted file mode 100644
index b476f57..0000000
--- a/sigllm/primitives/preprocessing.py
+++ /dev/null
@@ -1,83 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-"""
-Preprocessing module for time series forecasting.
-"""
-
-import numpy as np
-
-
-class Signal2String:
-    """Convert a signal into a string.
-
-    Prepare a univariate time series signal for LLMs.
-    Transforms a float array into a string of integers.
-
-    Args:
-        sep (str):
-            String to separate each element in values. Default to `','`.
-        space (bool):
-            Whether to add space between each digit in the result. Default to `False`.
-        decimal (int):
-            Number of decimal points to keep from the float representation. Default to `0`.
-        rescale(bool):
-            Whether to rescale the time series. Default to `False`.
-    """
-
-    def __init__(self, sep=',', space=False, decimal=0, rescale=False):
-        self.sep = sep
-        self.space = space
-        self.decimal = decimal
-        self.rescale = rescale
-
-    def transform(self, values):
-        """Convert a signal to a string.
-
-        Convert a 1-dimensional time series into text by casting and rescaling it
-        to nonnegative integer values then into a string.
-
-        Args:
-            values (numpy.ndarray):
-                A sequence of signal values.
-
-        Returns:
-            str:
-                Text containing the elements of `values`.
-        """
-        sign = 1 * (values >= 0) - 1 * (values < 0)
-        values = np.abs(values)
-
-        sequence = sign * (values * 10**self.decimal).astype(int)
-
-        # rescale all elements to be nonnegative
-        if self.rescale:
-            sequence = sequence - min(sequence)
-
-        res = self.sep.join(list(map(str, sequence)))
-        if self.space:
-            res = ' '.join(res)
-
-        return res
-
-    def reverse_transform(self, text, trunc=None):
-        """Convert a text string to a signal.
-
-        Convert a string containing digits into an array of numbers.
-
-        Args:
-            text (str):
-                A string containing signal values.
-            trunc (int):
-                Whether to truncate the text to a specific length. Default `None`.
-
-        Returns:
-            numpy.ndarray:
-                A 1-dimensional array containing parsed elements in `text`.
-        """
-        values = list(filter(None, text.split(self.sep)))
-        if trunc:
-            values = values[:trunc]
-
-        values = np.array(list(map(float, values)))
-        return values * 10**(-self.decimal)
diff --git a/sigllm/primitives/transformation.py b/sigllm/primitives/transformation.py
new file mode 100644
index 0000000..06a29ec
--- /dev/null
+++ b/sigllm/primitives/transformation.py
@@ -0,0 +1,175 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Transformation module.
+"""
+
+import re
+
+import numpy as np
+
+
+def format_as_string(values, sep=',', space=False):
+    """Format values to a list of string.
+
+    Transform a 2-D array of integers to a list of strings,
+    seperated by the indicated seperator and space.
+
+    Args:
+        sep (str):
+            String to separate each element in values. Default to `','`.
+        space (bool):
+            Whether to add space between each digit in the result. Default to `False`.
+
+    Returns:
+        ndarray:
+            A list of string representation of each row.
+    """
+    def _as_string(x):
+        text = sep.join(list(map(str, x)))
+
+        if space:
+            text = ' '.join(text)
+
+        return text
+
+    return np.apply_along_axis(_as_string, axis=1, arr=values)
+
+
+def _from_string_to_integer(text, sep=',', trunc=None, errors='ignore'):
+    """Convert a text sequence consisting of digits to an array of integers."""
+
+    nospace = re.sub(r"\s+", "", text)
+    rule = f'[^0-9|{sep}]'
+
+    if errors == 'raise':
+        search = re.search(rule, nospace)
+        if bool(search):
+            start, end = search.span()
+            raise ValueError(f'Encountered a non-digit value {nospace[start:end]}.')
+
+    values = list(filter(None, nospace.split(sep)))
+
+    if errors == 'ignore':
+        clean = list(filter(lambda x: not bool(re.search(rule, x)), values))
+
+    elif errors == 'filter':
+        clean = list(map(lambda x: re.sub(rule, '', x), values))
+
+    elif errors == 'coerce':
+        clean = list(map(lambda x: x if not bool(re.search(rule, x)) else np.nan, values))
+
+    else:
+        raise KeyError(f"Unknown errors strategy {errors}.")
+
+    clean = np.array(clean).astype(float)
+
+    if trunc:
+        clean = clean[:trunc]
+
+    return clean
+
+
+def format_as_integer(strings, sep=',', trunc=None, errors='ignore'):
+    """Format a nested list of text into an array of integers.
+
+    Transforms a list of list pf string input as 2-D array of integers,
+    seperated by the indicated seperator and truncated based on `trunc`.
+
+    Args:
+        sep (str):
+            String to separate each element in values. Default to `','`.
+        trunc (int):
+            How many values to keep from the ndarray. Default to `None`,
+            which retains all values.
+        errors (str):
+            Strategy to deal with erroneous values (not digits). Default
+            to `'ignore'`.
+            - If 'ignore', then invalid values will be ignored in the result.
+            - If 'filter', then invalid values will be filtered out of the string.
+            - If 'raise', then encountering invalud values will raise an exception.
+            - If 'coerce', then invalid values will be set as NaN.
+
+    Returns:
+        ndarray:
+            An array of digits values.
+    """
+    result = list()
+    for string_list in strings:
+        sample = list()
+        string = string_list
+        if not isinstance(string, list):
+            string = [string]
+
+        for text in string:
+            scalar = _from_string_to_integer(text, sep, trunc, errors)
+            sample.extend(scalar)
+
+        result.append(sample)
+
+    output = np.array(result, dtype=object)
+    if output.ndim >= 2:
+        output = output.astype(float)
+
+    return output
+
+
+class Float2Scalar:
+    """Convert an array of float values to scalar.
+
+    Transforms an array of floats to an array integers. With the
+    option to rescale such that the minimum value becomes zero
+    and you can keep certain decimal points.
+
+        1.05, 2., 3.1, 4.8342, 5, 0 -> 105, 200, 310, 483, 500, 0
+
+    Args:
+        decimal (int):
+            Number of decimal points to keep from the float representation. Default to `2`.
+        rescale (bool):
+            Whether to rescale the array such that the minimum value becomes 0. Default to `True`.
+    """
+
+    def __init__(self, decimal=2, rescale=True):
+        self.decimal = decimal
+        self.rescale = rescale
+        self.minimum = None
+
+    def fit(self, X):
+        self.minimum = np.min(X)
+
+    def transform(self, X):
+        if self.rescale:
+            X = (X - self.minimum)
+
+        sign = 1 * (X >= 0) - 1 * (X < 0)
+        values = np.abs(X)
+
+        values = sign * (values * 10**self.decimal).astype(int)
+
+        return values, self.minimum
+
+
+class Scalar2Float:
+    """Convert an array of integer values to float.
+
+    Transforms an array of integers to an array floats.
+    Shift values by minimum and include a predetermined
+    number of decimal points.
+
+        105, 200, 310, 483, 500, 0 -> 1.05, 2., 3.1, 4.8342, 5, 0
+
+    Args:
+        decimal (int):
+            Number of decimal points to keep from the float representation. Default to `2`.
+    """
+
+    def __init__(self, decimal=2):
+        self.decimal = decimal
+
+    def transform(self, X, minimum=0):
+        values = np.array(X).astype(float)
+        values = values * 10**(-self.decimal)
+
+        return values + minimum


Test patch:
diff --git a/tests/primitives/test_preprocessing.py b/tests/primitives/test_preprocessing.py
deleted file mode 100644
index 6440bdc..0000000
--- a/tests/primitives/test_preprocessing.py
+++ /dev/null
@@ -1,106 +0,0 @@
-import unittest
-
-import numpy as np
-
-from sigllm.primitives.preprocessing import Signal2String
-
-
-class Signal2StringTest(unittest.TestCase):
-
-    def test_transform_default(self):
-        converter = Signal2String()
-
-        data = np.array([
-            1, 2, 3, 4, 5
-        ])
-        expected = '1,2,3,4,5'
-
-        output = converter.transform(data)
-
-        assert converter.sep == ','
-        assert converter.space is False
-        assert converter.decimal == 0
-        assert converter.rescale is False
-
-        assert output == expected
-
-    def test_transform_space(self):
-        converter = Signal2String(space=True)
-
-        data = np.array([
-            1, 2, 3, 4, 5
-        ])
-        expected = '1 , 2 , 3 , 4 , 5'
-
-        output = converter.transform(data)
-
-        assert converter.space is True
-        assert output == expected
-
-    def test_transform_sep(self):
-        converter = Signal2String(sep='.')
-
-        data = np.array([
-            1, 2, 3, 4, 5
-        ])
-        expected = '1.2.3.4.5'
-
-        output = converter.transform(data)
-
-        assert converter.sep == '.'
-        assert output == expected
-
-    def test_transform_decimal(self):
-        converter = Signal2String(decimal=2)
-
-        data = np.array([
-            1, 2, 3, 4, 5
-        ])
-        expected = '100,200,300,400,500'
-
-        output = converter.transform(data)
-
-        assert converter.decimal == 2
-        assert output == expected
-
-    def test_transform_rescale(self):
-        converter = Signal2String(rescale=True)
-
-        data = np.array([
-            -1, 2, 3, 4, 5
-        ])
-        expected = '0,3,4,5,6'
-
-        output = converter.transform(data)
-
-        assert converter.rescale is True
-        assert output == expected
-
-    def test_reverse_transform_default(self):
-        converter = Signal2String()
-
-        data = '1,2,3,4,5'
-        expected = np.array([
-            1, 2, 3, 4, 5
-        ])
-
-        output = converter.reverse_transform(data)
-
-        assert converter.sep == ','
-        assert converter.space is False
-        assert converter.decimal == 0
-        assert converter.rescale is False
-
-        np.testing.assert_equal(output, expected)
-
-    def test_transform_reverse_transform(self):
-        converter = Signal2String()
-
-        data = np.array([
-            1, 2, 3, 4, 5
-        ])
-        expected = data
-
-        output = converter.reverse_transform(converter.transform(data))
-
-        np.testing.assert_equal(output, expected)
diff --git a/tests/primitives/test_transformation.py b/tests/primitives/test_transformation.py
new file mode 100644
index 0000000..906d8eb
--- /dev/null
+++ b/tests/primitives/test_transformation.py
@@ -0,0 +1,396 @@
+import unittest
+
+import numpy as np
+import pytest
+
+from sigllm.primitives.transformation import (
+    Float2Scalar, Scalar2Float, _from_string_to_integer, format_as_integer, format_as_string,)
+
+
+class FormatAsStringTest(unittest.TestCase):
+
+    def test_format_as_string_default(self):
+
+        data = np.array([[
+            1, 2, 3, 4, 5
+        ]])
+        expected = np.array([
+            '1,2,3,4,5'
+        ])
+
+        output = format_as_string(data)
+
+        np.testing.assert_array_equal(output, expected)
+
+    def test_format_as_string_space(self):
+
+        data = np.array([[
+            1, 2, 3, 4, 5
+        ]])
+        expected = np.array([
+            '1 , 2 , 3 , 4 , 5'
+        ])
+
+        output = format_as_string(data, space=True)
+
+        np.testing.assert_array_equal(output, expected)
+
+    def test_format_as_string_sep(self):
+
+        data = np.array([[
+            1, 2, 3, 4, 5
+        ]])
+        expected = np.array([
+            '1.2.3.4.5'
+        ])
+
+        output = format_as_string(data, sep='.')
+
+        assert output == expected
+
+    def test_format_as_string_decimal(self):
+
+        data = np.array([[
+            100, 200, 300, 400, 500
+        ]])
+        expected = np.array([
+            '100,200,300,400,500'
+        ])
+
+        output = format_as_string(data)
+
+        assert output == expected
+
+
+class FromStringToIntegerTest(unittest.TestCase):
+
+    def test__from_string_to_integer_default(self):
+
+        data = '1,2,3,4,5'
+
+        expected = np.array([
+            1, 2, 3, 4, 5
+        ])
+
+        output = _from_string_to_integer(data)
+
+        np.testing.assert_equal(output, expected)
+
+    def test__from_string_to_integer_trunc_all(self):
+
+        data = '1,2,3,4,5'
+
+        expected = np.array([
+            1, 2, 3, 4, 5
+        ])
+
+        output = _from_string_to_integer(data, trunc=5)
+
+        np.testing.assert_equal(output, expected)
+
+    def test__from_string_to_integer_trunc_two(self):
+
+        data = '134,252,3,412,51'
+
+        expected = np.array([
+            134, 252
+        ])
+
+        output = _from_string_to_integer(data, trunc=2)
+
+        np.testing.assert_equal(output, expected)
+
+    def test__from_string_to_integer_ignore(self):
+
+        data = '1 , 2!e , 3,4,5'
+
+        expected = np.array([
+            1, 3, 4, 5
+        ])
+
+        output = _from_string_to_integer(data, errors='ignore')
+
+        np.testing.assert_equal(output, expected)
+
+    def test__from_string_to_integer_filter(self):
+
+        data = '1 , 2!e , 3,4,5'
+
+        expected = np.array([
+            1, 2, 3, 4, 5
+        ])
+
+        output = _from_string_to_integer(data, errors='filter')
+
+        np.testing.assert_equal(output, expected)
+
+    def test__from_string_to_integer_coerce(self):
+
+        data = '1 , 2!e , 3,4,5'
+
+        expected = np.array([
+            1, np.nan, 3, 4, 5
+        ])
+
+        output = _from_string_to_integer(data, errors='coerce')
+
+        np.testing.assert_equal(output, expected)
+
+    def test__from_string_to_integer_raise(self):
+
+        data = '1 , 2!e , 3,4,5'
+
+        with pytest.raises(ValueError):
+            _from_string_to_integer(data, errors='raise')
+
+    def test__from_string_to_integer_error(self):
+
+        data = '1 , 2 , 3,4,5'
+
+        with pytest.raises(KeyError):
+            _from_string_to_integer(data, errors='unknown')
+
+
+def test_format_as_integer_one():
+    data = ['1,2,3,4,5']
+
+    expected = np.array([[
+        1, 2, 3, 4, 5
+    ]])
+
+    output = format_as_integer(data)
+
+    np.testing.assert_equal(output, expected)
+
+
+def test_format_as_integer_list():
+    data = [['1,2,3,4,5']]
+
+    expected = np.array([[
+        1, 2, 3, 4, 5
+    ]])
+
+    output = format_as_integer(data)
+
+    np.testing.assert_equal(output, expected)
+
+
+def test_format_as_integer_2d_shape_mismatch():
+    data = [['1,2,3,4,5'], ['1, 294., 3 , j34,5'], ['!232, 23,3,4,5']]
+
+    expected = np.array([
+        [1, 2, 3, 4, 5],
+        [1, 3, 5],
+        [23, 3, 4, 5]
+    ], dtype=object)
+
+    output = format_as_integer(data)
+
+    np.testing.assert_equal(output, expected)
+
+
+def test_format_as_integer_2d_trunc():
+    data = [['1,2,3,4,5'], ['1,294.,3,j34,5'], ['!232, 23,3,4,5']]
+
+    expected = np.array([
+        [1, 2],
+        [1, 3],
+        [23, 3]
+    ])
+
+    output = format_as_integer(data, trunc=2)
+
+    np.testing.assert_equal(output, expected)
+
+
+class Float2ScalarTest(unittest.TestCase):
+
+    def test_transform_default(self):
+        converter = Float2Scalar()
+
+        data = np.array([
+            1.05, 2., 3.1, 4.8342, 5, 0
+        ])
+        expected = np.array([
+            105, 200, 310, 483, 500, 0
+        ])
+        print(converter)
+
+        converter.fit(data)
+        output, minimum = converter.transform(data)
+
+        assert converter.decimal == 2
+        assert converter.rescale is True
+        assert converter.minimum == 0
+
+        np.testing.assert_array_equal(output, expected)
+
+    def test_transform_decimal_zero(self):
+        converter = Float2Scalar(decimal=0)
+
+        data = np.array([
+            1.05, 2., 3.1, 4.8342, 5, 0
+        ])
+        expected = np.array([
+            1, 2, 3, 4, 5, 0
+        ])
+
+        converter.fit(data)
+        output, minimum = converter.transform(data)
+
+        assert converter.decimal == 0
+        assert converter.rescale is True
+        assert converter.minimum == 0
+
+        np.testing.assert_array_equal(output, expected)
+
+    def test_transform_minimum_not_zero(self):
+        converter = Float2Scalar()
+
+        data = np.array([
+            1.05, 2., 3.1, 4.8342, 5
+        ])
+        expected = np.array([
+            0, 95, 204, 378, 395
+        ])
+
+        converter.fit(data)
+        output, minimum = converter.transform(data)
+
+        assert converter.decimal == 2
+        assert converter.rescale is True
+        assert converter.minimum == 1.05
+
+        np.testing.assert_array_equal(output, expected)
+
+    def test_transform_rescale_false(self):
+        converter = Float2Scalar(rescale=False)
+
+        data = np.array([
+            1.05, 2., 3.1, 4.8342, 5
+        ])
+        expected = np.array([
+            105, 200, 310, 483, 500
+        ])
+
+        converter.fit(data)
+        output, minimum = converter.transform(data)
+
+        assert converter.decimal == 2
+        assert converter.rescale is False
+        assert converter.minimum == 1.05
+
+        np.testing.assert_array_equal(output, expected)
+
+    def test_transform_negative(self):
+        converter = Float2Scalar()
+
+        data = np.array([
+            1.05, 2., 3.1, 4.8342, 5, -2
+        ])
+        expected = np.array([
+            305, 400, 509, 683, 700, 0
+        ])
+
+        converter.fit(data)
+        output, minimum = converter.transform(data)
+
+        assert converter.decimal == 2
+        assert converter.rescale is True
+        assert converter.minimum == -2
+
+        np.testing.assert_array_equal(output, expected)
+
+    def test_transform_fit_different(self):
+        converter = Float2Scalar()
+
+        data = np.array([
+            1.05, 2., 3.1, 4.8342, 5
+        ])
+        expected = np.array([
+            55, 150, 260, 433, 450
+        ])
+
+        converter.fit([7, 3, 0.5])
+        output, minimum = converter.transform(data)
+
+        assert converter.decimal == 2
+        assert converter.rescale is True
+        assert converter.minimum == 0.5
+
+        np.testing.assert_array_equal(output, expected)
+
+
+class Scalar2FloatTest(unittest.TestCase):
+
+    def test_transform_default(self):
+        converter = Scalar2Float()
+
+        data = np.array([
+            105, 200, 310, 483, 500, 0
+        ])
+        expected = np.array([
+            1.05, 2., 3.1, 4.83, 5, 0
+        ])
+
+        output = converter.transform(data)
+
+        assert converter.decimal == 2
+
+        np.testing.assert_array_equal(output, expected)
+
+    def test_transform_decimal_zero(self):
+        converter = Scalar2Float(decimal=0)
+
+        data = np.array([
+            1, 2, 3, 4, 5, 0
+        ])
+        expected = np.array([
+            1., 2., 3., 4., 5., 0.
+        ])
+
+        output = converter.transform(data)
+
+        assert converter.decimal == 0
+
+        np.testing.assert_array_equal(output, expected)
+
+    def test_transform_minimum_not_zero(self):
+        converter = Scalar2Float()
+
+        data = np.array([
+            105, 200, 310, 483, 500, 0
+        ])
+        expected = np.array([
+            0.05, 1., 2.1, 3.83, 4., -1.
+        ])
+
+        output = converter.transform(data, minimum=-1)
+
+        assert converter.decimal == 2
+
+        np.testing.assert_allclose(output, expected)
+
+
+def test_float2scalar_scalar2float_integration():
+    decimal = 2
+    rescale = True
+
+    float2scalar = Float2Scalar(decimal, rescale)
+
+    data = np.array([
+        1.05, 2., 3.1, 4.8342, 5, -1
+    ])
+
+    expected = np.array([
+        1.05, 2., 3.10, 4.83, 5., -1.
+    ])
+
+    float2scalar.fit(data)
+    transformed, minimum = float2scalar.transform(data)
+
+    scalar2float = Scalar2Float(decimal)
+
+    output = scalar2float.transform(transformed, minimum)
+
+    np.testing.assert_allclose(output, expected, rtol=1e-2)