Repository: takuti/flurs

Issue:
Revising project paths
- [ ] Evaluation related code into a unified module
- [ ] `metric` -> `metrics`
- [ ] Modules under `data` are bit confusing


Discussion:

Patch:
diff --git a/doc/reference.rst b/doc/reference.rst
index 9423bbc..d143c42 100644
--- a/doc/reference.rst
+++ b/doc/reference.rst
@@ -9,9 +9,9 @@ Data representation
 .. autosummary::
     :toctree: generated
 
-    data.entity.User
-    data.entity.Item
-    data.entity.Event
+    types.User
+    types.Item
+    types.Event
     datasets.movielens
 
 Baseline recommenders
@@ -57,5 +57,5 @@ Evaluation utilities
 .. autosummary::
     :toctree: generated
 
-    utils.metric
-    evaluator.Evaluator
+    metrics
+    evaluation.Evaluator
diff --git a/doc/user_guide.rst b/doc/user_guide.rst
index d9ce0f7..4fe54a5 100644
--- a/doc/user_guide.rst
+++ b/doc/user_guide.rst
@@ -5,7 +5,7 @@ You first need to convert your data to ``User``, ``Item`` and ``Event``:
 
 .. testcode::
 
-  from flurs.data.entity import User, Item, Event
+  from flurs.types import User, Item, Event
 
   # define a user with index 0
   user = User(0)
diff --git a/examples/faust/recommender.py b/examples/faust/recommender.py
index 0e62796..46ad445 100644
--- a/examples/faust/recommender.py
+++ b/examples/faust/recommender.py
@@ -6,7 +6,7 @@ Faust Consumer
 Consume events and incrementally train a FluRS recommender.
 """
 
-from flurs.data.entity import User, Item, Event
+from flurs.types import User, Item, Event
 from flurs.recommender import MFRecommender
 import json
 import numpy as np
diff --git a/examples/movielens/movielens1m.py b/examples/movielens/movielens1m.py
index ad70ee5..61872be 100644
--- a/examples/movielens/movielens1m.py
+++ b/examples/movielens/movielens1m.py
@@ -8,7 +8,7 @@ USAGE: %(program)s PATH_TO_MOVIELENS_1M_DIR
 
 from flurs.datasets import fetch_movielens
 from flurs.recommender import FMRecommender
-from flurs.evaluator import Evaluator
+from flurs.evaluation import Evaluator
 
 import logging
 import os
diff --git a/flurs/__init__.py b/flurs/__init__.py
index d08cba9..b76d856 100644
--- a/flurs/__init__.py
+++ b/flurs/__init__.py
@@ -11,4 +11,4 @@ logger.addHandler(handler)
 
 __version__ = "0.0.5"
 
-__all__ = ["baseline", "data", "datasets", "recommender", "utils"]
+__all__ = ["baseline", "types", "datasets", "recommender", "evaluation", "utils"]
diff --git a/flurs/base.py b/flurs/base.py
index 84269f9..1d7d94c 100644
--- a/flurs/base.py
+++ b/flurs/base.py
@@ -1,4 +1,4 @@
-from .data.entity import User, Item
+from .types import User, Item
 
 import numpy as np
 
diff --git a/flurs/data/tests/__init__.py b/flurs/data/tests/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/flurs/datasets/movielens.py b/flurs/datasets/movielens.py
index eb305b2..414180f 100644
--- a/flurs/datasets/movielens.py
+++ b/flurs/datasets/movielens.py
@@ -1,6 +1,6 @@
 """MovieLens datasets created by GroupLens."""
 
-from ..data.entity import User, Item, Event
+from ..types import User, Item, Event
 
 import os
 import time
diff --git a/flurs/evaluation/__init__.py b/flurs/evaluation/__init__.py
new file mode 100644
index 0000000..a9adde0
--- /dev/null
+++ b/flurs/evaluation/__init__.py
@@ -0,0 +1,5 @@
+from .evaluator import Evaluator
+
+__all__ = [
+    "Evaluator",
+]
diff --git a/flurs/evaluator.py b/flurs/evaluation/evaluator.py
similarity index 99%
rename from flurs/evaluator.py
rename to flurs/evaluation/evaluator.py
index d8975f0..1f8c47f 100644
--- a/flurs/evaluator.py
+++ b/flurs/evaluation/evaluator.py
@@ -1,10 +1,10 @@
-from .base import FeatureRecommenderMixin
-
 import time
 import numpy as np
 
 from collections import deque
-from . import logger
+
+from ..base import FeatureRecommenderMixin
+from .. import logger
 
 
 class Evaluator(object):
diff --git a/flurs/metrics/__init__.py b/flurs/metrics/__init__.py
new file mode 100644
index 0000000..b6c2471
--- /dev/null
+++ b/flurs/metrics/__init__.py
@@ -0,0 +1,20 @@
+from .ranking import (
+    recall,
+    precision,
+    average_precision,
+    auc,
+    reciprocal_rank,
+    mpr,
+    ndcg,
+)
+
+
+__all__ = [
+    "recall",
+    "precision",
+    "average_precision",
+    "auc",
+    "reciprocal_rank",
+    "mpr",
+    "ndcg",
+]
diff --git a/flurs/types/__init__.py b/flurs/types/__init__.py
new file mode 100644
index 0000000..f3f066c
--- /dev/null
+++ b/flurs/types/__init__.py
@@ -0,0 +1,5 @@
+from .actor import User, Item
+from .event import Event
+
+
+__all__ = ["User", "Item", "Event"]
diff --git a/flurs/data/entity.py b/flurs/types/actor.py
similarity index 53%
rename from flurs/data/entity.py
rename to flurs/types/actor.py
index 3e77206..96bd206 100644
--- a/flurs/data/entity.py
+++ b/flurs/types/actor.py
@@ -112,93 +112,3 @@ class Item(BaseActor):
             return "Item(index={})".format(self.index)
         else:
             return "Item(index={}, feature={})".format(self.index, self.feature)
-
-
-class Event(object):
-
-    """An event object that represents a single user-item interaction.
-
-    Parameters
-    ----------
-    user : User
-        A `User` instance.
-
-    item : Item
-        An `Item` instance.
-
-    value : float, default=1.0
-        A value representing the feedback. ``1.0`` in case of positive-only feedback,
-        or 5-scale value for rating prediction, for example.
-
-    context : numpy array, default=empty
-        Vector-represented contextual information associated with an interaction.
-        An element can be day of the week, time, weather, etc.
-
-    """
-
-    def __init__(self, user, item, value=1.0, context=np.array([])):
-        self.user = user
-        self.item = item
-        self.value = value
-        self.context = context
-
-    def encode(
-        self,
-        n_user=None,
-        n_item=None,
-        index=True,
-        feature=True,
-        context=True,
-        vertical=False,
-    ):
-        """Encode an event to an input vector for feature-based recommenders.
-
-        Parameters
-        ----------
-        n_user : int
-            Number of users currently registered to a recommender.
-
-        n_item : int
-            Number of items currently registered to a recommender.
-
-        index : bool, default=True
-            Include onehot-encoded user/item index to an input vector.
-
-        feature : bool, default=True
-            Include features associated with user and item.
-
-        context : bool, default=True
-            Include event-specific contextual information to a vector.
-
-        vertical : bool, default=False
-            Return as a transposed n-by-1 vertical vector.
-
-        Returns
-        -------
-        array
-            n-dimensional vector representing a user-item interaction.
-            Size can be ``(n, 1)`` or ``(1, n)``, depending on ``vertical`` parameter.
-
-        """
-
-        x = self.user.encode(dim=n_user, index=index, feature=feature, vertical=False)
-
-        if context:
-            x = np.concatenate((x, self.context))
-
-        iv = self.item.encode(dim=n_item, index=index, feature=feature, vertical=False)
-        x = np.concatenate((x, iv))
-
-        assert len(x) > 0, "feature vector has zero dimension"
-
-        return x if not vertical else np.array([x]).T
-
-    def __str__(self):
-        if len(self.context) == 0:
-            return "Event(user={}, item={}, value={})".format(
-                self.user, self.item, self.value
-            )
-        else:
-            return "Event(user={}, item={}, value={}, context={})".format(
-                self.user, self.item, self.value, self.context
-            )
diff --git a/flurs/types/event.py b/flurs/types/event.py
new file mode 100644
index 0000000..3e67f20
--- /dev/null
+++ b/flurs/types/event.py
@@ -0,0 +1,91 @@
+import numpy as np
+
+
+class Event(object):
+
+    """An event object that represents a single user-item interaction.
+
+    Parameters
+    ----------
+    user : User
+        A `User` instance.
+
+    item : Item
+        An `Item` instance.
+
+    value : float, default=1.0
+        A value representing the feedback. ``1.0`` in case of positive-only feedback,
+        or 5-scale value for rating prediction, for example.
+
+    context : numpy array, default=empty
+        Vector-represented contextual information associated with an interaction.
+        An element can be day of the week, time, weather, etc.
+
+    """
+
+    def __init__(self, user, item, value=1.0, context=np.array([])):
+        self.user = user
+        self.item = item
+        self.value = value
+        self.context = context
+
+    def encode(
+        self,
+        n_user=None,
+        n_item=None,
+        index=True,
+        feature=True,
+        context=True,
+        vertical=False,
+    ):
+        """Encode an event to an input vector for feature-based recommenders.
+
+        Parameters
+        ----------
+        n_user : int
+            Number of users currently registered to a recommender.
+
+        n_item : int
+            Number of items currently registered to a recommender.
+
+        index : bool, default=True
+            Include onehot-encoded user/item index to an input vector.
+
+        feature : bool, default=True
+            Include features associated with user and item.
+
+        context : bool, default=True
+            Include event-specific contextual information to a vector.
+
+        vertical : bool, default=False
+            Return as a transposed n-by-1 vertical vector.
+
+        Returns
+        -------
+        array
+            n-dimensional vector representing a user-item interaction.
+            Size can be ``(n, 1)`` or ``(1, n)``, depending on ``vertical`` parameter.
+
+        """
+
+        x = self.user.encode(dim=n_user, index=index, feature=feature, vertical=False)
+
+        if context:
+            x = np.concatenate((x, self.context))
+
+        iv = self.item.encode(dim=n_item, index=index, feature=feature, vertical=False)
+        x = np.concatenate((x, iv))
+
+        assert len(x) > 0, "feature vector has zero dimension"
+
+        return x if not vertical else np.array([x]).T
+
+    def __str__(self):
+        if len(self.context) == 0:
+            return "Event(user={}, item={}, value={})".format(
+                self.user, self.item, self.value
+            )
+        else:
+            return "Event(user={}, item={}, value={}, context={})".format(
+                self.user, self.item, self.value, self.context
+            )


Test patch:
diff --git a/flurs/baseline/tests/test_popular.py b/flurs/baseline/tests/test_popular.py
index 96a34f7..dc903e7 100644
--- a/flurs/baseline/tests/test_popular.py
+++ b/flurs/baseline/tests/test_popular.py
@@ -2,7 +2,7 @@ from unittest import TestCase
 import numpy as np
 from numpy.testing import assert_array_equal
 
-from flurs.data.entity import User, Item, Event
+from flurs.types import User, Item, Event
 from flurs.baseline import Popular
 
 
diff --git a/flurs/baseline/tests/test_random.py b/flurs/baseline/tests/test_random.py
index 3922e11..ed6679f 100644
--- a/flurs/baseline/tests/test_random.py
+++ b/flurs/baseline/tests/test_random.py
@@ -1,7 +1,7 @@
 from unittest import TestCase
 import numpy as np
 
-from flurs.data.entity import User, Item, Event
+from flurs.types import User, Item, Event
 from flurs.baseline import Random
 
 
diff --git a/flurs/tests/test_evaluator.py b/flurs/evaluation/tests/test_evaluator.py
similarity index 94%
rename from flurs/tests/test_evaluator.py
rename to flurs/evaluation/tests/test_evaluator.py
index f542a96..2a93432 100644
--- a/flurs/tests/test_evaluator.py
+++ b/flurs/evaluation/tests/test_evaluator.py
@@ -2,9 +2,9 @@ from unittest import TestCase
 import numpy as np
 from numpy.testing import assert_array_equal
 
-from flurs.data.entity import User, Item, Event
+from flurs.types import User, Item, Event
 from flurs.baseline import Popular
-from flurs.evaluator import Evaluator
+from flurs.evaluation import Evaluator
 
 
 class EvaluatorTestCase(TestCase):
diff --git a/flurs/utils/metric.py b/flurs/metrics/ranking.py
similarity index 100%
rename from flurs/utils/metric.py
rename to flurs/metrics/ranking.py
diff --git a/flurs/utils/tests/test_metric.py b/flurs/metrics/tests/test_ranking.py
similarity index 61%
rename from flurs/utils/tests/test_metric.py
rename to flurs/metrics/tests/test_ranking.py
index 72727d8..9062a09 100644
--- a/flurs/utils/tests/test_metric.py
+++ b/flurs/metrics/tests/test_ranking.py
@@ -1,7 +1,15 @@
 from unittest import TestCase
 import numpy as np
 
-from flurs.utils import metric
+from flurs.metrics import (
+    recall,
+    precision,
+    average_precision,
+    auc,
+    reciprocal_rank,
+    mpr,
+    ndcg,
+)
 
 
 class MetricTestCase(TestCase):
@@ -11,29 +19,29 @@ class MetricTestCase(TestCase):
         self.k = 2
 
     def test_recall(self):
-        actual = metric.recall(self.truth, self.recommend, self.k)
+        actual = recall(self.truth, self.recommend, self.k)
         self.assertAlmostEqual(actual, 0.333, delta=1e-3)
 
     def test_precision(self):
-        actual = metric.precision(self.truth, self.recommend, self.k)
+        actual = precision(self.truth, self.recommend, self.k)
         self.assertAlmostEqual(actual, 0.5, delta=1e-3)
 
     def test_average_precision(self):
-        actual = metric.average_precision(self.truth, self.recommend)
+        actual = average_precision(self.truth, self.recommend)
         self.assertAlmostEqual(actual, 0.756, delta=1e-3)
 
     def test_auc(self):
-        actual = metric.auc(self.truth, self.recommend)
+        actual = auc(self.truth, self.recommend)
         self.assertAlmostEqual(actual, 0.667, delta=1e-3)
 
     def test_reciprocal_rank(self):
-        actual = metric.reciprocal_rank(self.truth, self.recommend)
+        actual = reciprocal_rank(self.truth, self.recommend)
         self.assertAlmostEqual(actual, 1.0, delta=1e-3)
 
     def test_mpr(self):
-        actual = metric.mpr(self.truth, self.recommend)
+        actual = mpr(self.truth, self.recommend)
         self.assertAlmostEqual(actual, 33.333, delta=1e-3)
 
     def test_ndcg(self):
-        actual = metric.ndcg(self.truth, self.recommend, self.k)
+        actual = ndcg(self.truth, self.recommend, self.k)
         self.assertAlmostEqual(actual, 0.613, delta=1e-3)
diff --git a/flurs/recommender/tests/test_bprmf.py b/flurs/recommender/tests/test_bprmf.py
index 951669e..a5bfadd 100644
--- a/flurs/recommender/tests/test_bprmf.py
+++ b/flurs/recommender/tests/test_bprmf.py
@@ -1,7 +1,7 @@
 from unittest import TestCase
 import numpy as np
 
-from flurs.data.entity import User, Item, Event
+from flurs.types import User, Item, Event
 from flurs.recommender import BPRMFRecommender
 
 
diff --git a/flurs/recommender/tests/test_fm.py b/flurs/recommender/tests/test_fm.py
index 54233a1..ac4f7ba 100644
--- a/flurs/recommender/tests/test_fm.py
+++ b/flurs/recommender/tests/test_fm.py
@@ -1,7 +1,7 @@
 from unittest import TestCase
 import numpy as np
 
-from flurs.data.entity import User, Item, Event
+from flurs.types import User, Item, Event
 from flurs.recommender import FMRecommender
 
 
diff --git a/flurs/recommender/tests/test_mf.py b/flurs/recommender/tests/test_mf.py
index f49dcd6..14f71cf 100644
--- a/flurs/recommender/tests/test_mf.py
+++ b/flurs/recommender/tests/test_mf.py
@@ -1,7 +1,7 @@
 from unittest import TestCase
 import numpy as np
 
-from flurs.data.entity import User, Item, Event
+from flurs.types import User, Item, Event
 from flurs.recommender import MFRecommender
 
 
diff --git a/flurs/recommender/tests/test_sketch.py b/flurs/recommender/tests/test_sketch.py
index 5620d4b..08b160b 100644
--- a/flurs/recommender/tests/test_sketch.py
+++ b/flurs/recommender/tests/test_sketch.py
@@ -1,7 +1,7 @@
 from unittest import TestCase
 import numpy as np
 
-from flurs.data.entity import User, Item, Event
+from flurs.types import User, Item, Event
 from flurs.recommender import SketchRecommender
 
 
diff --git a/flurs/recommender/tests/test_user_knn.py b/flurs/recommender/tests/test_user_knn.py
index b429076..7286c09 100644
--- a/flurs/recommender/tests/test_user_knn.py
+++ b/flurs/recommender/tests/test_user_knn.py
@@ -1,7 +1,7 @@
 from unittest import TestCase
 import numpy as np
 
-from flurs.data.entity import User, Item, Event
+from flurs.types import User, Item, Event
 from flurs.recommender import UserKNNRecommender
 
 
diff --git a/flurs/data/__init__.py b/flurs/types/tests/__init__.py
similarity index 100%
rename from flurs/data/__init__.py
rename to flurs/types/tests/__init__.py
diff --git a/flurs/data/tests/test_entity.py b/flurs/types/tests/test_actor.py
similarity index 58%
rename from flurs/data/tests/test_entity.py
rename to flurs/types/tests/test_actor.py
index 4ac2ef2..9b07f56 100644
--- a/flurs/data/tests/test_entity.py
+++ b/flurs/types/tests/test_actor.py
@@ -2,10 +2,10 @@ from unittest import TestCase
 import numpy as np
 from numpy.testing import assert_array_equal
 
-from flurs.data.entity import User, Item, Event
+from flurs.types import User, Item
 
 
-class EntityTestCase(TestCase):
+class UserTestCase(TestCase):
     def test_user(self):
         user = User(1, np.arange(5))
         self.assertEqual(user.index, 1)
@@ -19,6 +19,8 @@ class EntityTestCase(TestCase):
         v = user.encode(dim=None, index=False, feature=True, vertical=True)
         assert_array_equal(v, np.array([[0], [1], [2], [3], [4]]))
 
+
+class ItemTestCase(TestCase):
     def test_item(self):
         item = Item(1, np.arange(5))
         self.assertEqual(item.index, 1)
@@ -31,20 +33,3 @@ class EntityTestCase(TestCase):
 
         v = item.encode(dim=None, index=False, feature=True, vertical=True)
         assert_array_equal(v, np.array([[0], [1], [2], [3], [4]]))
-
-    def test_event(self):
-        user = User(1, np.arange(3))
-        item = Item(1, np.arange(3))
-        event = Event(user, item, 5.0, np.arange(5))
-        self.assertEqual(event.value, 5.0)
-
-        v = event.encode(index=False, feature=True, context=True, vertical=False)
-        assert_array_equal(v, np.array([0, 1, 2, 0, 1, 2, 3, 4, 0, 1, 2]))
-
-        v = event.encode(index=True, feature=True, context=False, vertical=False)
-        assert_array_equal(v, np.array([0, 1, 0, 1, 2, 0, 1, 0, 1, 2]))
-
-        v = event.encode(
-            n_user=3, n_item=3, index=True, feature=False, context=False, vertical=True
-        )
-        assert_array_equal(v, np.array([[0], [1], [0], [0], [1], [0]]))
diff --git a/flurs/types/tests/test_event.py b/flurs/types/tests/test_event.py
new file mode 100644
index 0000000..6eea58a
--- /dev/null
+++ b/flurs/types/tests/test_event.py
@@ -0,0 +1,24 @@
+from unittest import TestCase
+import numpy as np
+from numpy.testing import assert_array_equal
+
+from flurs.types import User, Item, Event
+
+
+class EventTestCase(TestCase):
+    def test_event(self):
+        user = User(1, np.arange(3))
+        item = Item(1, np.arange(3))
+        event = Event(user, item, 5.0, np.arange(5))
+        self.assertEqual(event.value, 5.0)
+
+        v = event.encode(index=False, feature=True, context=True, vertical=False)
+        assert_array_equal(v, np.array([0, 1, 2, 0, 1, 2, 3, 4, 0, 1, 2]))
+
+        v = event.encode(index=True, feature=True, context=False, vertical=False)
+        assert_array_equal(v, np.array([0, 1, 0, 1, 2, 0, 1, 0, 1, 2]))
+
+        v = event.encode(
+            n_user=3, n_item=3, index=True, feature=False, context=False, vertical=True
+        )
+        assert_array_equal(v, np.array([[0], [1], [0], [0], [1], [0]]))