diff --git a/docs/guide/GettingStarted.ipynb b/docs/guide/GettingStarted.ipynb index c8f8957bc..2644c28d4 100644 --- a/docs/guide/GettingStarted.ipynb +++ b/docs/guide/GettingStarted.ipynb @@ -68,26 +68,6 @@ "from pyprojroot.here import here" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We also need to set up logging, both to unify it and to silence noisy debug-level messages. When running LensKit from a script, you usually want to use `INFO`-level logging, and to use the `LoggingConfig` class for more flexibility." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "import logging\n", - "\n", - "from lenskit.logging import basic_logging\n", - "\n", - "basic_logging(logging.WARNING)" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -99,7 +79,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "metadata": {}, "outputs": [ { @@ -178,7 +158,7 @@ "4 1 5 3.0 889751712" ] }, - "execution_count": 5, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } @@ -199,7 +179,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -216,7 +196,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -242,11 +222,20 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "metadata": { "scrolled": true }, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/michael/Documents/LensKit/lkpy/lenskit/lenskit/als/_explicit.py:94: UserWarning: Sparse CSR tensor support is in beta state. If you miss a functionality in the sparse tensor support, please submit a feature request to https://github.com/pytorch/pytorch/issues. (Triggered internally at /Users/runner/miniforge3/conda-bld/libtorch_1733624403138/work/aten/src/ATen/SparseCsrTensorImpl.cpp:55.)\n", + " rmat = rmat.to_sparse_csr()\n" + ] + } + ], "source": [ "# test data is organized by user\n", "all_test = ItemListCollection(UserIDKey)\n", @@ -282,7 +271,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ @@ -302,7 +291,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "metadata": {}, "outputs": [ { @@ -340,15 +329,15 @@ " \n", " \n", " ALS\n", - " 0.128447\n", - " 0.091585\n", - " 0.196082\n", + " 0.129831\n", + " 0.096835\n", + " 0.208196\n", " \n", " \n", " II\n", - " 0.093132\n", - " 0.035806\n", - " 0.104097\n", + " 0.096751\n", + " 0.035333\n", + " 0.104951\n", " \n", " \n", "\n", @@ -357,11 +346,11 @@ "text/plain": [ " NDCG RBP RecipRank\n", "model \n", - "ALS 0.128447 0.091585 0.196082\n", - "II 0.093132 0.035806 0.104097" + "ALS 0.129831 0.096835 0.208196\n", + "II 0.096751 0.035333 0.104951" ] }, - "execution_count": 10, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -372,12 +361,12 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "metadata": {}, "outputs": [ { "data": { - "image/png": "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r9tvv71fFgoAAK5Or/8Qzt13360//vGPqqmp0eeffy5J+u53v6tJkyb5fXEAAKDveh17t9utgQMH6s4779Sdd97pGT9//rza29v9+odjAABA3/XqA3pvvvmmkpKS9PXXX/s89vXXX+uuu+7S22+/7bfFAQCAvutV7IuLi/WTn/xE3/nOd3we+853vqPly5dr8+bNflscAADou17F/pNPPtHUqVMv+fh9992njz/+uK9rAgAAftSr2J8+fVpdXV2XfPzcuXM6ffp0nxcFAAD8p1exv+2223TkyJFLPn7kyBHFxsb2eVEAAMB/ehX7f/qnf1J+fr7+8pe/+DzW1NSkVatWad68eX5bHAAA6LteffVuxYoV2rdvn7773e9q/vz5GjdunBwOh2pra/Xaa68pJiZGK1as6K+1AgCAq9Cr2A8aNEiHDx9WXl6eSktLPe/PR0ZGav78+Vq7dq0GDRrULwsFAABXp9c/qhMREaGioiJt2bJFLS0tsixLw4YN8/x0LgAACCy9jv1Fra2tOnHihBwOh4KCgrz+rj0AAAgcvfqAniR9+umnuu++++R0OpWcnKy7775bt956q77//e97/a17AAAQGHp1Zt/U1KQpU6Zo2LBh2rBhg8aPHy/LsvTHP/5R27dvV1pamj755BPdeuut/bVeAADQS72K/c9//nPFxsbq8OHDCg8P94zPmDFDixcv1r333quf//znKiws9PtCAQDA1enVZfzy8nItX77cK/QX3XTTTVq2bJneffddvy0OAAD0Xa9i/8UXX2jy5MmXfDwpKUlffPFFnxcFAAD8p1exP3PmzGX/Xv2gQYPU3t7e50UBAAD/6fVX786cOdPjZXxJcrvdsiyrz4sCAAD+06vYW5alsWPHXvZxflwHAIDA0qvYf/DBB/21DgAA0E96FfspU6b01zoAAEA/6VXsBwwY8K2X6R0Oh7q6uvq0KAAA4D+9iv2bb755yccqKir0i1/8gg/oAQAQYHoV+zlz5viMffbZZ8rLy9Pbb7+tRx99VM8995zfFgcAAPqu138I56I///nPeuKJJzRhwgR1dXWppqZGr776qkaNGuXP9QEAgD7qdezb2tq0fPly3X777fr000/1/vvv6+2331ZiYmJ/rA8AAPRRry7jv/jii3rhhRcUHR2t3bt393hZHwAABJZexX7FihW66aabdPvtt+vVV1/Vq6++2uO8N954wy+LAwAAfder2C9YsIBfyAMA4DrTq9i/8sor/bQMAADQX6760/gAAOD6QOwBADAcsQcAwHDEHgAAwxF7AAAMR+wBADAcsQcAwHC2x76oqEhxcXEKDw+Xy+XSoUOHLjv/4MGDcrlcCg8P1+jRo7V161afOX/961/15JNPavjw4QoPD1d8fLzKysr66xAAAAhotsa+tLRU2dnZys/PV3V1tdLS0jRz5kzV19f3OL+urk6zZs1SWlqaqqurtXLlSi1ZskR79+71zOns7NTf//3f6/jx4/rVr36lY8eOafv27Ro5cuS1OiwAAAJKr35Bz982bNighQsXatGiRZKkjRs36t1331VxcbEKCwt95m/dulWjRo3Sxo0bJUnx8fE6cuSI1q9fr3nz5kmSduzYoS+//FIVFRUKCQmRJMXGxl52HR0dHero6PDcd7vd/jg8AAACgm1n9p2dnaqqqlJ6errXeHp6uioqKnrcprKy0mf+9OnTdeTIEZ07d06S9NZbbyklJUVPPvmknE6nEhMTtXbtWnV3d19yLYWFhYqIiPDcYmJi+nh0AAAEDtti39LSou7ubjmdTq9xp9OppqamHrdpamrqcX5XV5daWlokSV988YV+9atfqbu7W2VlZVq1apVeeuklPf/885dcS15entra2jy3kydP9vHoAAAIHLZexpfk81f0LMu67F/W62n+346fP39et956q7Zt26agoCC5XC79+c9/1rp16/TTn/60x32GhYUpLCysL4cBAEDAsi32UVFRCgoK8jmLb25u9jl7vyg6OrrH+cHBwRo6dKgkafjw4QoJCVFQUJBnTnx8vJqamtTZ2anQ0FA/HwkAAIHNtsv4oaGhcrlcKi8v9xovLy9Xampqj9ukpKT4zN+/f7+SkpI8H8a755579Kc//Unnz5/3zPn88881fPhwQg8AuCHZ+tW73Nxcvfzyy9qxY4dqa2uVk5Oj+vp6ZWVlSbrwXvqCBQs887OysnTixAnl5uaqtrZWO3bsUElJiZYuXeqZs3jxYrW2turpp5/W559/rv/4j//Q2rVr9eSTT17z4wMAIBDY+p59RkaGWltbVVBQoMbGRiUmJqqsrMzzVbnGxkav79zHxcWprKxMOTk52rJli0aMGKFNmzZ5vnYnSTExMdq/f79ycnI0YcIEjRw5Uk8//bSWL19+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", + "image/png": "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oUO6//77e2SiAACgczr8dbkPPfSQ/vCHP6iiokKff/65JOnb3/62xo8f3+2TAwAAXdfh2Hs8HoWGhmrcuHEaN26cd/nVq1d16dIl/mIcAAB+pkMX6O3fv18JCQn66quv2rz21Vdf6cEHH9TBgwe7bXIAAKDrOhT7/Px8/fCHP9S3vvWtNq9961vf0vLly7V58+ZumxwAAOi6DsX+k08+0eTJk2/4+qOPPqqPP/64q3MCAADdqEOxv3DhglpaWm74+pUrV3ThwoUuTwoAAHSfDsX+vvvu04kTJ274+okTJxQdHd3lSQEAgO7Todj/4z/+o3JycvTnP/+5zWtut1urVq3SnDlzum1yAACg6zp0692KFSt04MABffvb39a8efM0atQoORwOVVZW6q233lJUVJRWrFjRU3MFAACd0KHY9+3bV8eOHVN2drYKCwu9v58PDw/XvHnztHbtWvXt27dHJgoAADqnw1+qExYWpry8PG3ZskX19fWyLEuDBg3yfnUuAADwLx2O/XUNDQ06e/asHA6HAgICfP6uPQAA8B8dukBPkj799FM9+uijcjqdmjhxoh566CHde++9+u53v+vzt+4BAIB/6NCRvdvt1qRJkzRo0CBt2LBBo0ePlmVZ+sMf/qDt27crOTlZn3zyie69996emi8AAOigDsX+5z//uaKjo3Xs2DGFhIR4l0+bNk2LFi3SI488op///OfKzc3t9okCAIDO6dBp/OLiYi1fvtwn9Nf16dNHy5Yt03vvvddtkwMAAF3XodifPn1aEyZMuOHrCQkJOn36dJcnBQAAuk+HYn/x4sWb/r36vn376tKlS12eFAAA6D4dvvXu4sWL7Z7GlySPxyPLsro8KQAA0H06FHvLsjRy5Mibvs6X6wAA4F86FPsPP/ywp+YBAAB6SIdiP2nSpJ6aBwAA6CEdin2vXr2+8TS9w+FQS0tLlyYFAAC6T4div3///hu+VlJSon/5l3/hAj0AAPxMh2I/a9asNss+++wzZWdn6+DBg3rmmWf0yiuvdNvkAABA13X4D+Fc97//+796/vnnNWbMGLW0tKiiokJvvvmmhg0b1p3zAwAAXdTh2Dc2Nmr58uW6//779emnn+qDDz7QwYMHFR8f3xPzAwAAXdSh0/ivvfaaXn31VUVGRmr37t3tntYHAAD+pUOxX7Fihfr06aP7779fb775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", "text/plain": [ "
" ] diff --git a/docs/guide/logging.rst b/docs/guide/logging.rst index cb179424e..a7fa50987 100644 --- a/docs/guide/logging.rst +++ b/docs/guide/logging.rst @@ -9,6 +9,7 @@ LensKit provides support code for logging and progress reporting. This code lives in the :py:mod:`lenskit.logging` package and provides several capabilities: +- Logger acquisition functions with useful defaults. - Backend-independent progress reporting, with colorful progress bars (via Rich_) on terminals. - Easy logging configuration for recommender scripts, supporting log files and @@ -74,6 +75,21 @@ are run by other tools like DVC. level; this allows you to send ``DEBUG`` messages to the file while only ``INFO`` messages go to the console. +Emitting Log Messages +~~~~~~~~~~~~~~~~~~~~~ + +When writing LensKit code that needs to emit log messages, use LensKit's +:func:`~lenskit.logging.get_logger` function. This wraps Structopt's +``get_logger`` in a proxy that has more useful LensKit defaults (only emitting +warnings and errors when logging has not been configured). The resulting logger +can be used like any other Structlog or standard library logger. + +Structlog loggers are *lazy*, resolving their configurations when they are +*bound* with variables. When emitting many log messages in a loop or function, +we recommend calling :meth:`structlog.typing.BindableLogger.bind` to get a bound +logger with the configuration resolved, which will be much faster for repeated +fine-grained logging messages. + Progress Reporting ~~~~~~~~~~~~~~~~~~ diff --git a/lenskit/lenskit/data/movielens.py b/lenskit/lenskit/data/movielens.py index 581a27e4c..c57adc3fa 100644 --- a/lenskit/lenskit/data/movielens.py +++ b/lenskit/lenskit/data/movielens.py @@ -17,12 +17,13 @@ import numpy as np import pandas as pd -import structlog + +from lenskit.logging import get_logger from .convert import from_interactions_df from .dataset import Dataset -_log = structlog.stdlib.get_logger(__name__) +_log = get_logger(__name__) LOC: TypeAlias = Path | tuple[ZipFile, str] diff --git a/lenskit/lenskit/knn/item.py b/lenskit/lenskit/knn/item.py index ec0c62e01..f06e8d004 100644 --- a/lenskit/lenskit/knn/item.py +++ b/lenskit/lenskit/knn/item.py @@ -14,7 +14,6 @@ import warnings import numpy as np -import structlog import torch from scipy.sparse import csr_array from typing_extensions import Optional, override @@ -22,14 +21,14 @@ from lenskit import util from lenskit.data import Dataset, FeedbackType, ItemList, QueryInput, RecQuery, Vocabulary from lenskit.diagnostics import DataWarning -from lenskit.logging import trace +from lenskit.logging import get_logger, trace from lenskit.logging.progress import item_progress_handle, pbh_update from lenskit.math.sparse import normalize_sparse_rows, safe_spmv from lenskit.parallel import ensure_parallel_init from lenskit.pipeline import Component, Trainable from lenskit.util.torch import inference_mode -_log = structlog.stdlib.get_logger(__name__) +_log = get_logger(__name__) MAX_BLOCKS = 1024 diff --git a/lenskit/lenskit/knn/user.py b/lenskit/lenskit/knn/user.py index fa117ff29..839cb6acd 100644 --- a/lenskit/lenskit/knn/user.py +++ b/lenskit/lenskit/knn/user.py @@ -24,11 +24,12 @@ from lenskit.data import Dataset, FeedbackType, ItemList, QueryInput, RecQuery from lenskit.data.vocab import Vocabulary from lenskit.diagnostics import DataWarning +from lenskit.logging import get_logger from lenskit.math.sparse import normalize_sparse_rows, safe_spmv, torch_sparse_to_scipy from lenskit.parallel.config import ensure_parallel_init from lenskit.pipeline import Component, Trainable -_log = structlog.stdlib.get_logger(__name__) +_log = get_logger(__name__) class UserKNNScorer(Component, Trainable): diff --git a/lenskit/lenskit/logging/__init__.py b/lenskit/lenskit/logging/__init__.py index 184968bc1..c9a8a84c3 100644 --- a/lenskit/lenskit/logging/__init__.py +++ b/lenskit/lenskit/logging/__init__.py @@ -7,6 +7,7 @@ import structlog +from ._proxy import get_logger from .config import LoggingConfig, basic_logging from .progress import Progress, item_progress, set_progress_impl from .tasks import Task @@ -22,7 +23,6 @@ "trace", ] -get_logger = structlog.stdlib.get_logger _trace_debug = os.environ.get("LK_TRACE", "no").lower() == "debug" @@ -32,6 +32,9 @@ def trace(logger: structlog.stdlib.BoundLogger, *args: Any, **kwargs: Any): messages are more fine-grained than debug-level messages, and you usually don't want them. + This function does not work on the lazy proxies returned by + :func:`get_logger` and similar — it only works on bound loggers. + Stability: Caller """ diff --git a/lenskit/lenskit/logging/_proxy.py b/lenskit/lenskit/logging/_proxy.py new file mode 100644 index 000000000..512a25e3c --- /dev/null +++ b/lenskit/lenskit/logging/_proxy.py @@ -0,0 +1,32 @@ +import logging +from typing import Any + +import structlog +from structlog._config import BoundLoggerLazyProxy + +_fallback_wrapper = structlog.make_filtering_bound_logger(logging.WARNING) + + +def get_logger(name: str) -> structlog.stdlib.BoundLogger: + """ + Get a logger. This works like :func:`structlog.stdlib.get_logger`, except + the returned proxy logger is quiet (only WARN and higher messages) if + structlog has not been configured. LensKit code should use this instead of + obtaining loggers from Structlog directly. + """ + return LenskitProxyLogger(None, logger_factory_args=[name]) # type: ignore + + +class LenskitProxyLogger(BoundLoggerLazyProxy): + """ + Lazy proxy logger for LensKit. This is based on Structlog's lazy proxy, + with using a filtering logger by default when structlog is not configured. + """ + + def bind(self, **new_values: Any): + if structlog.is_configured(): + self._wrapper_class = None + else: + self._wrapper_class = _fallback_wrapper + + return super().bind(**new_values) diff --git a/lenskit/lenskit/logging/monitor.py b/lenskit/lenskit/logging/monitor.py index 3ac3ea851..6e4ddf0f0 100644 --- a/lenskit/lenskit/logging/monitor.py +++ b/lenskit/lenskit/logging/monitor.py @@ -21,15 +21,15 @@ from typing import Protocol, runtime_checkable from uuid import UUID, uuid4 -import structlog import zmq +from ._proxy import get_logger from .tasks import Task SIGNAL_ADDR = "inproc://lenskit-monitor-signal" REFRESH_INTERVAL = 5 -_log = structlog.stdlib.get_logger(__name__) +_log = get_logger(__name__) _monitor_lock = threading.Lock() _monitor_instance: Monitor | None = None @@ -258,7 +258,7 @@ def _handle_log_message(self): logger = logging.getLogger(name) logger.handle(rec) elif engine == "structlog": - logger = structlog.get_logger(name) + logger = get_logger(name) data = json.loads(data) method = getattr(logger, data["method"]) method(**data["event"]) diff --git a/lenskit/lenskit/logging/progress/_rich.py b/lenskit/lenskit/logging/progress/_rich.py index 37cf765a8..3db1d4e62 100644 --- a/lenskit/lenskit/logging/progress/_rich.py +++ b/lenskit/lenskit/logging/progress/_rich.py @@ -21,9 +21,10 @@ from typing_extensions import override from .._console import console, get_live +from .._proxy import get_logger from ._base import Progress -_log = structlog.stdlib.get_logger("lenskit.logging.progress") +_log = get_logger("lenskit.logging.progress") _pb_lock = Lock() _progress: ProgressImpl | None = None _active_bars: dict[UUID, RichProgress] = {} diff --git a/lenskit/lenskit/logging/resource.py b/lenskit/lenskit/logging/resource.py index 98e8391d1..d66ee563f 100644 --- a/lenskit/lenskit/logging/resource.py +++ b/lenskit/lenskit/logging/resource.py @@ -10,10 +10,11 @@ from dataclasses import dataclass from pathlib import Path -import structlog import torch -_log = structlog.get_logger(__name__) +from ._proxy import get_logger + +_log = get_logger(__name__) @dataclass diff --git a/lenskit/lenskit/logging/tasks.py b/lenskit/lenskit/logging/tasks.py index 6222d8ba5..b1be75635 100644 --- a/lenskit/lenskit/logging/tasks.py +++ b/lenskit/lenskit/logging/tasks.py @@ -12,12 +12,12 @@ from typing import Annotated, Any from uuid import UUID, uuid4 -import structlog from pydantic import BaseModel, BeforeValidator, Field, SerializeAsAny +from ._proxy import get_logger from .resource import ResourceMeasurement, reset_linux_hwm -_log = structlog.stdlib.get_logger(__name__) +_log = get_logger(__name__) _active_tasks: list[Task] = [] diff --git a/lenskit/lenskit/logging/worker.py b/lenskit/lenskit/logging/worker.py index 5bf4883af..4587b5969 100644 --- a/lenskit/lenskit/logging/worker.py +++ b/lenskit/lenskit/logging/worker.py @@ -21,6 +21,7 @@ import zmq from structlog.typing import EventDict +from ._proxy import get_logger from .config import CORE_PROCESSORS, active_logging_config, log_warning from .monitor import get_monitor from .processors import add_process_info @@ -28,7 +29,7 @@ from .tracing import lenskit_filtering_logger _active_context: WorkerContext | None = None -_log = structlog.stdlib.get_logger(__name__) +_log = get_logger(__name__) @dataclass diff --git a/lenskit/lenskit/parallel/pool.py b/lenskit/lenskit/parallel/pool.py index f33168d24..e0a6a2ae5 100644 --- a/lenskit/lenskit/parallel/pool.py +++ b/lenskit/lenskit/parallel/pool.py @@ -11,10 +11,9 @@ from multiprocessing.context import SpawnContext, SpawnProcess from multiprocessing.managers import SharedMemoryManager -import structlog from typing_extensions import Any, Generic, Iterable, Iterator, override -from lenskit.logging.tasks import Task +from lenskit.logging import Task, get_logger from lenskit.logging.worker import WorkerContext, WorkerLogConfig from . import worker @@ -22,7 +21,7 @@ from .invoker import A, InvokeOp, M, ModelOpInvoker, R from .serialize import shm_serialize -_log = structlog.stdlib.get_logger(__name__) +_log = get_logger(__name__) def multiprocess_executor( diff --git a/lenskit/lenskit/parallel/worker.py b/lenskit/lenskit/parallel/worker.py index af5b02c23..c85bb8f8f 100644 --- a/lenskit/lenskit/parallel/worker.py +++ b/lenskit/lenskit/parallel/worker.py @@ -12,13 +12,14 @@ from dataclasses import dataclass from typing import Any -import structlog from typing_extensions import Generic +from lenskit.logging import get_logger + from .invoker import A, InvokeOp, M, R from .serialize import SHMData, shm_deserialize -_log = structlog.get_logger(__name__) +_log = get_logger(__name__) __work_context: WorkerData diff --git a/lenskit/lenskit/pipeline/__init__.py b/lenskit/lenskit/pipeline/__init__.py index 0cc783e91..66c177e2e 100644 --- a/lenskit/lenskit/pipeline/__init__.py +++ b/lenskit/lenskit/pipeline/__init__.py @@ -16,10 +16,10 @@ from types import FunctionType, UnionType from uuid import NAMESPACE_URL, uuid4, uuid5 -import structlog from typing_extensions import Any, Literal, Self, TypeAlias, TypeVar, cast, overload from lenskit.data import Dataset +from lenskit.logging import get_logger from . import config from .components import ( # type: ignore # noqa: F401 @@ -51,7 +51,7 @@ "topn_pipeline", ] -_log = structlog.stdlib.get_logger(__name__) +_log = get_logger(__name__) # common type var for quick use T = TypeVar("T") diff --git a/lenskit/lenskit/testing/_movielens.py b/lenskit/lenskit/testing/_movielens.py index 08901830b..7443e02e6 100644 --- a/lenskit/lenskit/testing/_movielens.py +++ b/lenskit/lenskit/testing/_movielens.py @@ -7,7 +7,6 @@ import numpy as np import pandas as pd -import structlog from pyprojroot import here import pytest @@ -17,10 +16,11 @@ from lenskit.data import Dataset, ItemListCollection, UserIDKey, from_interactions_df from lenskit.data.lazy import LazyDataset from lenskit.data.movielens import load_movielens, load_movielens_df +from lenskit.logging import get_logger from lenskit.pipeline import RecPipelineBuilder from lenskit.splitting import TTSplit, simple_test_pair -_log = structlog.stdlib.get_logger("lenskit.testing") +_log = get_logger("lenskit.testing") ml_test_dir = here("data/ml-latest-small") ml_100k_zip = here("data/ml-100k.zip")