{"id":13780374,"url":"https://github.com/IFCA-Advanced-Computing/frouros","last_synced_at":"2025-05-11T13:32:12.551Z","repository":{"id":49773940,"uuid":"470511138","full_name":"IFCA-Advanced-Computing/frouros","owner":"IFCA-Advanced-Computing","description":"Frouros: an open-source Python library for drift detection in machine learning systems.","archived":false,"fork":false,"pushed_at":"2024-04-13T07:47:17.000Z","size":24060,"stargazers_count":158,"open_issues_count":10,"forks_count":10,"subscribers_count":4,"default_branch":"main","last_synced_at":"2024-04-14T12:09:37.614Z","etag":null,"topics":["change-detection","concept-drift","data-drift","dataset-drift","dataset-shift","distribution-shift","drift-detection","machine-learning","machine-learning-engineering","machine-learning-operations","mle","mlops","python","statistics"],"latest_commit_sha":null,"homepage":"https://frouros.readthedocs.io","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/IFCA-Advanced-Computing.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":"CITATION.cff","codeowners":"CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2022-03-16T09:21:26.000Z","updated_at":"2024-04-17T08:44:47.908Z","dependencies_parsed_at":"2024-01-29T10:06:10.331Z","dependency_job_id":"d1c6a9b5-72ff-4976-9eba-f45de7417906","html_url":"https://github.com/IFCA-Advanced-Computing/frouros","commit_stats":{"total_commits":646,"total_committers":5,"mean_commits":129.2,"dds":0.05417956656346745,"last_synced_commit":"529f9e39e30481eeddcdba1081c3baecbd52a6ad"},"previous_names":["ifca/frouros"],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IFCA-Advanced-Computing%2Ffrouros","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IFCA-Advanced-Computing%2Ffrouros/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IFCA-Advanced-Computing%2Ffrouros/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IFCA-Advanced-Computing%2Ffrouros/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/IFCA-Advanced-Computing","download_url":"https://codeload.github.com/IFCA-Advanced-Computing/frouros/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225056808,"owners_count":17414214,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["change-detection","concept-drift","data-drift","dataset-drift","dataset-shift","distribution-shift","drift-detection","machine-learning","machine-learning-engineering","machine-learning-operations","mle","mlops","python","statistics"],"created_at":"2024-08-03T18:01:15.048Z","updated_at":"2024-11-17T15:31:09.489Z","avatar_url":"https://github.com/IFCA-Advanced-Computing.png","language":"Python","funding_links":[],"categories":["Drift Detection"],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg height=\"115px\" src=\"https://raw.githubusercontent.com/IFCA-Advanced-Computing/frouros/main/images/logo.png\" alt=\"logo\"\u003e\n\u003c/p\u003e\n\n---\n\n\u003cp align=\"center\"\u003e\n  \u003c!-- CI --\u003e\n  \u003ca href=\"https://github.com/IFCA-Advanced-Computing/frouros/actions/workflows/ci.yml\"\u003e\n    \u003cimg src=\"https://github.com/IFCA-Advanced-Computing/frouros/actions/workflows/ci.yml/badge.svg?style=flat-square\" alt=\"ci\"/\u003e\n  \u003c/a\u003e\n  \u003c!-- Code coverage --\u003e\n  \u003ca href=\"https://codecov.io/gh/IFCA-Advanced-Computing/frouros\"\u003e\n    \u003cimg src=\"https://codecov.io/gh/IFCA-Advanced-Computing/frouros/graph/badge.svg?token=DLKQSWYTYM\" alt=\"coverage\"/\u003e\n  \u003c/a\u003e\n  \u003c!-- Documentation --\u003e\n  \u003ca href=\"https://frouros.readthedocs.io/\"\u003e\n    \u003cimg src=\"https://readthedocs.org/projects/frouros/badge/?version=latest\" alt=\"documentation\"/\u003e\n  \u003c/a\u003e\n  \u003c!-- Downloads --\u003e\n  \u003ca href=\"https://pepy.tech/project/frouros\"\u003e\n    \u003cimg src=\"https://static.pepy.tech/badge/frouros\" alt=\"downloads\"/\u003e\n  \u003c/a\u003e\n  \u003c!-- Platform --\u003e\n  \u003ca href=\"https://github.com/IFCA-Advanced-Computing/frouros\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/platform-Linux%20%7C%20macOS%20%7C%20Windows-blue.svg\" alt=\"downloads\"/\u003e\n  \u003c/a\u003e\n  \u003c!-- PyPI --\u003e\n  \u003ca href=\"https://pypi.org/project/frouros\"\u003e\n    \u003cimg src=\"https://img.shields.io/pypi/v/frouros.svg?label=release\u0026color=blue\" alt=\"pypi\"\u003e\n  \u003c/a\u003e\n  \u003c!-- Python --\u003e\n  \u003ca href=\"https://pypi.org/project/frouros\"\u003e\n    \u003cimg src=\"https://img.shields.io/pypi/pyversions/frouros\" alt=\"python\"\u003e\n  \u003c/a\u003e\n  \u003c!-- License --\u003e\n  \u003ca href=\"https://opensource.org/licenses/BSD-3-Clause\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/license-BSD%203--Clause-blue.svg\" alt=\"bsd_3_license\"\u003e\n  \u003c/a\u003e\n  \u003c!-- Journal --\u003e\n  \u003ca href=\"https://doi.org/10.1016/j.softx.2024.101733\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/SoftwareX-10.1016%2Fj.softx.2024.101733-blue.svg\" alt=\"SoftwareX\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\nFrouros is a Python library for drift detection in machine learning systems that provides a combination of classical and more recent algorithms for both concept and data drift detection.\n\n\u003cp align=\"center\"\u003e\n    \u003ci\u003e\n        \"Everything changes and nothing stands still\"\n    \u003c/i\u003e\n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n    \u003ci\u003e\n        \"You could not step twice into the same river\"\n    \u003c/i\u003e\n\u003c/p\u003e\n\u003cdiv align=\"center\" style=\"width: 70%;\"\u003e\n    \u003cp align=\"right\"\u003e\n        \u003ci\u003e\n            Heraclitus of Ephesus (535-475 BCE.)\n        \u003c/i\u003e\n    \u003c/p\u003e\n\u003c/div\u003e\n\n----\n\n## ⚡️ Quickstart\n\n### 🔄 Concept drift\n\nAs a quick example, we can use the breast cancer dataset to which concept drift it is induced and show the use of a concept drift detector like DDM (Drift Detection Method). We can see how concept drift affects the performance in terms of accuracy.\n\n```python\nimport numpy as np\nfrom sklearn.datasets import load_breast_cancer\nfrom sklearn.linear_model import LogisticRegression\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.pipeline import Pipeline\nfrom sklearn.preprocessing import StandardScaler\n\nfrom frouros.detectors.concept_drift import DDM, DDMConfig\nfrom frouros.metrics import PrequentialError\n\nnp.random.seed(seed=31)\n\n# Load breast cancer dataset\nX, y = load_breast_cancer(return_X_y=True)\n\n# Split train (70%) and test (30%)\n(\n    X_train,\n    X_test,\n    y_train,\n    y_test,\n) = train_test_split(X, y, train_size=0.7, random_state=31)\n\n# Define and fit model\npipeline = Pipeline(\n    [\n        (\"scaler\", StandardScaler()),\n        (\"model\", LogisticRegression()),\n    ]\n)\npipeline.fit(X=X_train, y=y_train)\n\n# Detector configuration and instantiation\nconfig = DDMConfig(\n    warning_level=2.0,\n    drift_level=3.0,\n    min_num_instances=25,  # minimum number of instances before checking for concept drift\n)\ndetector = DDM(config=config)\n\n# Metric to compute accuracy\nmetric = PrequentialError(alpha=1.0)  # alpha=1.0 is equivalent to normal accuracy\n\ndef stream_test(X_test, y_test, y, metric, detector):\n    \"\"\"Simulate data stream over X_test and y_test. y is the true label.\"\"\"\n    drift_flag = False\n    for i, (X, y) in enumerate(zip(X_test, y_test)):\n        y_pred = pipeline.predict(X.reshape(1, -1))\n        error = 1 - (y_pred.item() == y.item())\n        metric_error = metric(error_value=error)\n        _ = detector.update(value=error)\n        status = detector.status\n        if status[\"drift\"] and not drift_flag:\n            drift_flag = True\n            print(f\"Concept drift detected at step {i}. Accuracy: {1 - metric_error:.4f}\")\n    if not drift_flag:\n        print(\"No concept drift detected\")\n    print(f\"Final accuracy: {1 - metric_error:.4f}\\n\")\n\n# Simulate data stream (assuming test label available after each prediction)\n# No concept drift is expected to occur\nstream_test(\n    X_test=X_test,\n    y_test=y_test,\n    y=y,\n    metric=metric,\n    detector=detector,\n)\n# \u003e\u003e No concept drift detected\n# \u003e\u003e Final accuracy: 0.9766\n\n# IMPORTANT: Induce/simulate concept drift in the last part (20%)\n# of y_test by modifying some labels (50% approx). Therefore, changing P(y|X))\ndrift_size = int(y_test.shape[0] * 0.2)\ny_test_drift = y_test[-drift_size:]\nmodify_idx = np.random.rand(*y_test_drift.shape) \u003c= 0.5\ny_test_drift[modify_idx] = (y_test_drift[modify_idx] + 1) % len(np.unique(y_test))\ny_test[-drift_size:] = y_test_drift\n\n# Reset detector and metric\ndetector.reset()\nmetric.reset()\n\n# Simulate data stream (assuming test label available after each prediction)\n# Concept drift is expected to occur because of the label modification\nstream_test(\n    X_test=X_test,\n    y_test=y_test,\n    y=y,\n    metric=metric,\n    detector=detector,\n)\n# \u003e\u003e Concept drift detected at step 142. Accuracy: 0.9510\n# \u003e\u003e Final accuracy: 0.8480\n```\n\nMore concept drift examples can be found [here](https://frouros.readthedocs.io/en/latest/examples/concept_drift.html).\n\n### 📊 Data drift\n\nAs a quick example, we can use the iris dataset to which data drift is induced and show the use of a data drift detector like Kolmogorov-Smirnov test.\n\n```python\nimport numpy as np\nfrom sklearn.datasets import load_iris\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.tree import DecisionTreeClassifier\n\nfrom frouros.detectors.data_drift import KSTest\n\nnp.random.seed(seed=31)\n\n# Load iris dataset\nX, y = load_iris(return_X_y=True)\n\n# Split train (70%) and test (30%)\n(\n    X_train,\n    X_test,\n    y_train,\n    y_test,\n) = train_test_split(X, y, train_size=0.7, random_state=31)\n\n# Set the feature index to which detector is applied\nfeature_idx = 0\n\n# IMPORTANT: Induce/simulate data drift in the selected feature of y_test by\n# applying some gaussian noise. Therefore, changing P(X))\nX_test[:, feature_idx] += np.random.normal(\n    loc=0.0,\n    scale=3.0,\n    size=X_test.shape[0],\n)\n\n# Define and fit model\nmodel = DecisionTreeClassifier(random_state=31)\nmodel.fit(X=X_train, y=y_train)\n\n# Set significance level for hypothesis testing\nalpha = 0.001\n# Define and fit detector\ndetector = KSTest()\n_ = detector.fit(X=X_train[:, feature_idx])\n\n# Apply detector to the selected feature of X_test\nresult, _ = detector.compare(X=X_test[:, feature_idx])\n\n# Check if drift is taking place\nif result.p_value \u003c= alpha:\n    print(f\"Data drift detected at feature {feature_idx}\")\nelse:\n    print(f\"No data drift detected at feature {feature_idx}\")\n# \u003e\u003e Data drift detected at feature 0\n# Therefore, we can reject H0 (both samples come from the same distribution).\n```\n\nMore data drift examples can be found [here](https://frouros.readthedocs.io/en/latest/examples/data_drift.html).\n\n## 🛠 Installation\n\nFrouros can be installed via pip:\n\n```bash\npip install frouros\n```\n\n## 🕵🏻‍♂️️ Drift detection methods\n\nThe currently implemented detectors are listed in the following table.\n\n\u003ctable style=\"width: 100%; text-align: center; border-collapse: collapse; border: 1px solid grey;\"\u003e\n  \u003cthead\u003e\n    \u003ctr\u003e\n    \u003cth style=\"text-align: center; border: 1px solid grey; padding: 4px;\"\u003eDrift detector\u003c/th\u003e\n    \u003cth style=\"text-align: center; border: 1px solid grey; padding: 4px;\"\u003eType\u003c/th\u003e\n    \u003cth style=\"text-align: center; border: 1px solid grey; padding: 4px;\"\u003eFamily\u003c/th\u003e\n    \u003cth style=\"text-align: center; border: 1px solid grey; padding: 4px;\"\u003eUnivariate (U) / Multivariate (M)\u003c/th\u003e\n    \u003cth style=\"text-align: center; border: 1px solid grey; padding: 4px;\"\u003eNumerical (N) / Categorical (C)\u003c/th\u003e\n    \u003cth style=\"text-align: center; border: 1px solid grey; padding: 4px;\"\u003eMethod\u003c/th\u003e\n    \u003cth style=\"text-align: center; border: 1px solid grey; padding: 4px;\"\u003eReference\u003c/th\u003e\n    \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n  \u003ctr\u003e\n    \u003ctd rowspan=\"13\" style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eConcept drift\u003c/td\u003e\n    \u003ctd rowspan=\"13\" style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eStreaming\u003c/td\u003e\n    \u003ctd rowspan=\"4\" style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eChange detection\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eU\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eN\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eBOCD\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003e\u003ca href=\"https://doi.org/10.48550/arXiv.0710.3742\"\u003eAdams and MacKay (2007)\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eU\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eN\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eCUSUM\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003e\u003ca href=\"https://doi.org/10.2307/2333009\"\u003ePage (1954)\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eU\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eN\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eGeometric moving average\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003e\u003ca href=\"https://doi.org/10.2307/1266443\"\u003eRoberts (1959)\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eU\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eN\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003ePage Hinkley\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003e\u003ca href=\"https://doi.org/10.2307/2333009\"\u003ePage (1954)\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd rowspan=\"6\" style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eStatistical process control\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eU\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eN\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eDDM\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003e\u003ca href=\"https://doi.org/10.1007/978-3-540-28645-5_29\"\u003eGama et al. (2004)\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eU\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eN\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eECDD-WT\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003e\u003ca href=\"https://doi.org/10.1016/j.patrec.2011.08.019\"\u003eRoss et al. (2012)\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eU\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eN\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eEDDM\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003e\u003ca href=\"https://www.researchgate.net/publication/245999704_Early_Drift_Detection_Method\"\u003eBaena-Garcıa et al. (2006)\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eU\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eN\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eHDDM-A\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003e\u003ca href=\"https://doi.org/10.1109/TKDE.2014.2345382\"\u003eFrias-Blanco et al. (2014)\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eU\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eN\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eHDDM-W\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003e\u003ca href=\"https://doi.org/10.1109/TKDE.2014.2345382\"\u003eFrias-Blanco et al. (2014)\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eU\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eN\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eRDDM\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003e\u003ca href=\"https://doi.org/10.1016/j.eswa.2017.08.023\"\u003eBarros et al. (2017)\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd rowspan=\"3\" style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eWindow based\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eU\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eN\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eADWIN\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003e\u003ca href=\"https://doi.org/10.1137/1.9781611972771.42\"\u003eBifet and Gavalda (2007)\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eU\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eN\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eKSWIN\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003e\u003ca href=\"https://doi.org/10.1016/j.neucom.2019.11.111\"\u003eRaab et al. (2020)\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eU\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eN\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eSTEPD\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003e\u003ca href=\"https://doi.org/10.1007/978-3-540-75488-6_27\"\u003eNishida and Yamauchi (2007)\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd rowspan=\"19\" style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eData drift\u003c/td\u003e\n    \u003ctd rowspan=\"17\" style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eBatch\u003c/td\u003e\n    \u003ctd rowspan=\"9\" style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eDistance based\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eU\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eN\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eBhattacharyya distance\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003e\u003ca href=\"https://www.jstor.org/stable/25047882\"\u003eBhattacharyya (1946)\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eU\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eN\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eEarth Mover's distance\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003e\u003ca href=\"https://doi.org/10.1023/A:1026543900054\"\u003eRubner et al. (2000)\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eU\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eN\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eEnergy distance\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003e\u003ca href=\"https://doi.org/10.1016/j.jspi.2013.03.018\"\u003eSzékely et al. (2013)\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eU\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eN\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eHellinger distance\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003e\u003ca href=\"https://doi.org/10.1515/CRLL.1909.136.210\"\u003eHellinger (1909)\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eU\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eN\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eHistogram intersection normalized complement\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003e\u003ca href=\"https://doi.org/10.1007/BF00130487\"\u003eSwain and Ballard (1991)\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eU\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eN\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eJensen-Shannon distance\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003e\u003ca href=\"https://doi.org/10.1109/18.61115\"\u003eLin (1991)\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eU\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eN\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eKullback-Leibler divergence\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003e\u003ca href=\"https://doi.org/10.1214/aoms/1177729694\"\u003eKullback and Leibler (1951)\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eM\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eN\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eMaximum Mean Discrepancy\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003e\u003ca href=\"https://dl.acm.org/doi/10.5555/2188385.2188410\"\u003eGretton et al. (2012)\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eU\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eN\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003ePopulation Stability Index\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003e\u003ca href=\"https://doi.org/10.1057/jors.2008.144\"\u003eWu and Olson (2010)\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd rowspan=\"8\" style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eStatistical test\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eU\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eN\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eAnderson-Darling test\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003e\u003ca href=\"https://doi.org/10.2307/2288805\"\u003eScholz and Stephens (1987)\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eU\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eN\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eBaumgartner-Weiss-Schindler test\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003e\u003ca href=\"https://doi.org/10.2307/2533862\"\u003eBaumgartner et al. (1998)\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eU\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eC\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eChi-square test\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003e\u003ca href=\"https://doi.org/10.1080/14786440009463897\"\u003ePearson (1900)\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eU\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eN\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eCramér-von Mises test\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003e\u003ca href=\"https://doi.org/10.1080/03461238.1928.10416862\"\u003eCramér (1902)\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eU\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eN\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eKolmogorov-Smirnov test\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003e\u003ca href=\"https://doi.org/10.2307/2280095\"\u003eMassey Jr (1951)\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eU\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eN\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eKuiper's test\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003e\u003ca href=\"https://doi.org/10.1016/S1385-7258(60)50006-0\"\u003eKuiper (1960)\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eU\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eN\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eMann-Whitney U test\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003e\u003ca href=\"https://doi.org/10.1214/aoms/1177730491\"\u003eMann and Whitney (1947)\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eU\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eN\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eWelch's t-test\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003e\u003ca href=\"https://doi.org/10.2307/2332510\"\u003eWelch (1947)\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd rowspan=\"2\" style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eStreaming\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eDistance based\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eM\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eN\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eMaximum Mean Discrepancy\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003e\u003ca href=\"https://dl.acm.org/doi/10.5555/2188385.2188410\"\u003eGretton et al. (2012)\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eStatistical test\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eU\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eN\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003eIncremental Kolmogorov-Smirnov test\u003c/td\u003e\n    \u003ctd style=\"text-align: center; border: 1px solid grey; padding: 8px;\"\u003e\u003ca href=\"https://doi.org/10.1145/2939672.2939836\"\u003edos Reis et al. (2016)\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n\n## ❗ What is and what is not Frouros?\n\nUnlike other libraries that in addition to provide drift detection algorithms, include other functionalities such as anomaly/outlier detection, adversarial detection, imbalance learning, among others, Frouros has and will **ONLY** have one purpose: **drift detection**.\n\nWe firmly believe that machine learning related libraries or frameworks should not follow [Jack of all trades, master of none](https://en.wikipedia.org/wiki/Jack_of_all_trades,_master_of_none) principle. Instead, they should be focused on a single task and do it well.\n\n## ✅ Who is using Frouros?\n\nFrouros is actively being used by the following projects to implement drift\ndetection in machine learning pipelines:\n\n * [AI4EOSC](https://ai4eosc.eu).\n * [iMagine](https://imagine-ai.eu).\n\nIf you want your project listed here, do not hesitate to send us a pull request.\n\n## 👍 Contributing\n\nCheck out the [contribution](https://github.com/IFCA/frouros/blob/main/CONTRIBUTING.md) section.\n\n## 💬 Citation\n\nIf you want to cite Frouros you can use the [SoftwareX publication](https://doi.org/10.1016/j.softx.2024.101733).\n\n```bibtex\n@article{CESPEDESSISNIEGA2024101733,\ntitle = {Frouros: An open-source Python library for drift detection in machine learning systems},\njournal = {SoftwareX},\nvolume = {26},\npages = {101733},\nyear = {2024},\nissn = {2352-7110},\ndoi = {https://doi.org/10.1016/j.softx.2024.101733},\nurl = {https://www.sciencedirect.com/science/article/pii/S2352711024001043},\nauthor = {Jaime {Céspedes Sisniega} and Álvaro {López García}},\nkeywords = {Machine learning, Drift detection, Concept drift, Data drift, Python},\nabstract = {Frouros is an open-source Python library capable of detecting drift in machine learning systems. It provides a combination of classical and more recent algorithms for drift detection, covering both concept and data drift. We have designed it to be compatible with any machine learning framework and easily adaptable to real-world use cases. The library is developed following best development and continuous integration practices to ensure ease of maintenance and extensibility.}\n}\n```\n\n## 📝 License\n\nFrouros is an open-source software licensed under the [BSD-3-Clause license](https://github.com/IFCA/frouros/blob/main/LICENSE).\n\n## 🙏 Acknowledgements\n\nFrouros has received funding from the Agencia Estatal de Investigación, Unidad de Excelencia María de Maeztu, ref. MDM-2017-0765.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FIFCA-Advanced-Computing%2Ffrouros","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FIFCA-Advanced-Computing%2Ffrouros","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FIFCA-Advanced-Computing%2Ffrouros/lists"}