Repository: ruleminer/InterpreTS (language: python)

Issue:
Streaming feature extractor



Discussion / hints:

Fix patch:
diff --git a/examples/core/basic_usage.ipynb b/examples/core/basic_usage.ipynb
index e981579..1e67c99 100644
--- a/examples/core/basic_usage.ipynb
+++ b/examples/core/basic_usage.ipynb
@@ -33,7 +33,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 1,
    "metadata": {},
    "outputs": [
     {
@@ -56,7 +56,13 @@
       "Freq: D, dtype: int64\n",
       "\n",
       "Extracted Features:\n",
-      "Length of the time series: 10\n"
+      "Length of the time series: 0    5\n",
+      "1    5\n",
+      "2    5\n",
+      "3    5\n",
+      "4    5\n",
+      "5    5\n",
+      "Name: length, dtype: int64\n"
      ]
     }
    ],
@@ -123,7 +129,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.9.13"
+   "version": "3.10.1"
   }
  },
  "nbformat": 4,
diff --git a/examples/core/features/feature_absolute_energy.ipynb b/examples/core/features/feature_absolute_energy.ipynb
new file mode 100644
index 0000000..81559a2
--- /dev/null
+++ b/examples/core/features/feature_absolute_energy.ipynb
@@ -0,0 +1,58 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import pandas as pd\n",
+    "from interpreTS import FeatureExtractor, Features, TimeSeriesData"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "raw_data = pd.DataFrame({'time': pd.date_range(start=\"2023-01-01\", periods=8, freq=\"D\"),\n",
+    "                         'value': [15, 3, 6, 2, 1, 5, 8, 10]})\n",
+    "ts_data = TimeSeriesData(raw_data.set_index('time'))\n",
+    "\n",
+    "\n",
+    "extractor1 = FeatureExtractor(features=[Features.ABSOLUTE_ENERGY])\n",
+    "feature1 = extractor1.extract_features(ts_data.data)\n",
+    "\n",
+    "extractor2 = FeatureExtractor(features=[Features.ABSOLUTE_ENERGY], window_size=4,\n",
+    "                              feature_params={\"absolute_energy\": {\"start\": 1, \"end\": 3}})\n",
+    "feature2 = extractor2.extract_features(ts_data.data)\n",
+    "\n",
+    "\n",
+    "print(f\"Absolute energy of time series: \\n{feature1[Features.ABSOLUTE_ENERGY]}\")\n",
+    "print(f\"Absolute energy with params of time series: \\n{feature2[Features.ABSOLUTE_ENERGY]}\")"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.10.1"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/examples/core/features/feature_entropy.ipynb b/examples/core/features/feature_entropy.ipynb
new file mode 100644
index 0000000..3aa6161
--- /dev/null
+++ b/examples/core/features/feature_entropy.ipynb
@@ -0,0 +1,58 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import pandas as pd\n",
+    "import sys\n",
+    "import os"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Entropy of time series: 0.9182958340544896\n"
+     ]
+    }
+   ],
+   "source": [
+    "sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname('__file__'), '../../../interpreTS')))\n",
+    "from core.features.feature_entropy import calculate_entropy\n",
+    "\n",
+    "data = pd.Series([1, 2, 3, 5, 10, 15])\n",
+    "entropy = calculate_entropy(data, 2)\n",
+    "print(f\"Entropy of time series: {entropy}\")"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.10.1"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/examples/core/features/feature_flat_spots.ipynb b/examples/core/features/feature_flat_spots.ipynb
new file mode 100644
index 0000000..aa85607
--- /dev/null
+++ b/examples/core/features/feature_flat_spots.ipynb
@@ -0,0 +1,64 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "The longest flat spot length is: 4\n"
+     ]
+    }
+   ],
+   "source": [
+    "import pandas as pd\n",
+    "import sys\n",
+    "import os\n",
+    "\n",
+    "# Adding path to interpreTS directory so notebook can import modules\n",
+    "sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname('__file__'), '../../../interpreTS')))\n",
+    "from core.features.feature_flat_spots import calculate_flat_spots\n",
+    "\n",
+    "# Data preparation\n",
+    "data = pd.Series([1, 1, 1, 2, 3, 3, 4, 4, 4, 4, 4, 5, 1, 1])\n",
+    "\n",
+    "# Function call\n",
+    "longest_flat_spot = calculate_flat_spots(data)\n",
+    "\n",
+    "# Displaying the result\n",
+    "print(f\"The longest flat spot length is: {longest_flat_spot}\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.12.2"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/examples/core/features/feature_heterogeneity.ipynb b/examples/core/features/feature_heterogeneity.ipynb
new file mode 100644
index 0000000..0d4e5e5
--- /dev/null
+++ b/examples/core/features/feature_heterogeneity.ipynb
@@ -0,0 +1,53 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import pandas as pd\n",
+    "from interpreTS import FeatureExtractor, Features, TimeSeriesData"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "#extract peak feature from time series data\n",
+    "raw_data = pd.DataFrame({'time': pd.date_range(start=\"2023-01-01\", periods=8, freq=\"D\"),\n",
+    "                         'value': [15, 3, 6, 2, 1, 5, 8, 10]})\n",
+    "ts_data = TimeSeriesData(raw_data.set_index('time'))\n",
+    "\n",
+    "\n",
+    "extractor = FeatureExtractor(features=[Features.HETEROGENEITY])\n",
+    "feature = extractor.extract_features(ts_data.data)\n",
+    "\n",
+    "print(f\"Heterogeneity of time series: \\n{feature[Features.HETEROGENEITY]}\")"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.10.1"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/examples/core/features/feature_missing_points.ipynb b/examples/core/features/feature_missing_points.ipynb
new file mode 100644
index 0000000..9758a39
--- /dev/null
+++ b/examples/core/features/feature_missing_points.ipynb
@@ -0,0 +1,70 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import pandas as pd\n",
+    "from interpreTS import FeatureExtractor, Features, TimeSeriesData"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [
+    {
+     "ename": "AttributeError",
+     "evalue": "type object 'Features' has no attribute 'MISSING_POINTS'",
+     "output_type": "error",
+     "traceback": [
+      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
+      "\u001b[1;31mAttributeError\u001b[0m                            Traceback (most recent call last)",
+      "Cell \u001b[1;32mIn[4], line 6\u001b[0m\n\u001b[0;32m      3\u001b[0m ts_data \u001b[38;5;241m=\u001b[39m TimeSeriesData(raw_data\u001b[38;5;241m.\u001b[39mset_index(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mtime\u001b[39m\u001b[38;5;124m'\u001b[39m))\n\u001b[0;32m      5\u001b[0m \u001b[38;5;66;03m# Extract missing points as a percentage\u001b[39;00m\n\u001b[1;32m----> 6\u001b[0m extractor1 \u001b[38;5;241m=\u001b[39m FeatureExtractor(features\u001b[38;5;241m=\u001b[39m[\u001b[43mFeatures\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mMISSING_POINTS\u001b[49m])\n\u001b[0;32m      7\u001b[0m feature1 \u001b[38;5;241m=\u001b[39m extractor1\u001b[38;5;241m.\u001b[39mextract_features(ts_data\u001b[38;5;241m.\u001b[39mdata)\n\u001b[0;32m      9\u001b[0m \u001b[38;5;66;03m# Extract missing points count over a specified window size\u001b[39;00m\n",
+      "\u001b[1;31mAttributeError\u001b[0m: type object 'Features' has no attribute 'MISSING_POINTS'"
+     ]
+    }
+   ],
+   "source": [
+    "raw_data = pd.DataFrame({'time': pd.date_range(start=\"2023-01-01\", periods=16, freq=\"D\"),\n",
+    "                         'value': [15, None, 6, 2, None, 5, 8, 10, None, None, 12, 14, 16, 18, 20, 22]})\n",
+    "ts_data = TimeSeriesData(raw_data.set_index('time'))\n",
+    "\n",
+    "# Extract missing points as a percentage\n",
+    "extractor1 = FeatureExtractor(features=[Features.MISSING_POINTS])\n",
+    "feature1 = extractor1.extract_features(ts_data.data)\n",
+    "\n",
+    "# Extract missing points count over a specified window size\n",
+    "extractor2 = FeatureExtractor(features=[Features.MISSING_POINTS], window_size=4,\n",
+    "                               feature_params={\"missing_points\": {\"percentage\": False}})\n",
+    "feature2 = extractor2.extract_features(ts_data.data)\n",
+    "\n",
+    "print(f\"Missing points percentage of time series: \\n{feature1[Features.MISSING_POINTS]}\")\n",
+    "print(f\"Missing points count with params of time series: \\n{feature2[Features.MISSING_POINTS]}\")"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.10.1"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/examples/core/features/feature_peak.ipynb b/examples/core/features/feature_peak.ipynb
new file mode 100644
index 0000000..8e95f3f
--- /dev/null
+++ b/examples/core/features/feature_peak.ipynb
@@ -0,0 +1,79 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import pandas as pd\n",
+    "from interpreTS import FeatureExtractor, Features, TimeSeriesData"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Peaks of time series: \n",
+      "0    15\n",
+      "1     6\n",
+      "2     8\n",
+      "3    10\n",
+      "Name: peak, dtype: int64\n",
+      "Peaks with params of time series: \n",
+      "0    6\n",
+      "1    6\n",
+      "2    2\n",
+      "3    5\n",
+      "4    8\n",
+      "Name: peak, dtype: int64\n"
+     ]
+    }
+   ],
+   "source": [
+    "#extract peak feature from time series data\n",
+    "raw_data = pd.DataFrame({'time': pd.date_range(start=\"2023-01-01\", periods=8, freq=\"D\"),\n",
+    "                         'value': [15, 3, 6, 2, 1, 5, 8, 10]})\n",
+    "ts_data = TimeSeriesData(raw_data.set_index('time'))\n",
+    "\n",
+    "\n",
+    "extractor1 = FeatureExtractor(features=[Features.PEAK])\n",
+    "feature1 = extractor1.extract_features(ts_data.data)\n",
+    "\n",
+    "extractor2 = FeatureExtractor(features=[Features.PEAK], window_size=4,\n",
+    "                              feature_params={\"peak\": {\"start\": 1, \"end\": 3}})\n",
+    "feature2 = extractor2.extract_features(ts_data.data)\n",
+    "\n",
+    "\n",
+    "print(f\"Peaks of time series: \\n{feature1[Features.PEAK]}\")\n",
+    "print(f\"Peaks with params of time series: \\n{feature2[Features.PEAK]}\")"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.10.1"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/examples/core/features/feature_stability.ipynb b/examples/core/features/feature_stability.ipynb
new file mode 100644
index 0000000..cb20352
--- /dev/null
+++ b/examples/core/features/feature_stability.ipynb
@@ -0,0 +1,71 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 8,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import pandas as pd\n",
+    "import sys\n",
+    "import os"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [
+    {
+     "ename": "ModuleNotFoundError",
+     "evalue": "No module named 'statsmodels'",
+     "output_type": "error",
+     "traceback": [
+      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
+      "\u001b[1;31mModuleNotFoundError\u001b[0m                       Traceback (most recent call last)",
+      "Cell \u001b[1;32mIn[9], line 2\u001b[0m\n\u001b[0;32m      1\u001b[0m sys\u001b[38;5;241m.\u001b[39mpath\u001b[38;5;241m.\u001b[39minsert(\u001b[38;5;241m0\u001b[39m, os\u001b[38;5;241m.\u001b[39mpath\u001b[38;5;241m.\u001b[39mabspath(os\u001b[38;5;241m.\u001b[39mpath\u001b[38;5;241m.\u001b[39mjoin(os\u001b[38;5;241m.\u001b[39mpath\u001b[38;5;241m.\u001b[39mdirname(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m__file__\u001b[39m\u001b[38;5;124m'\u001b[39m), \u001b[38;5;124m'\u001b[39m\u001b[38;5;124m../../../interpreTS\u001b[39m\u001b[38;5;124m'\u001b[39m)))\n\u001b[1;32m----> 2\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mcore\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mfeatures\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mfeature_stability\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m calculate_stability\n\u001b[0;32m      4\u001b[0m \u001b[38;5;66;03m# Data preparation\u001b[39;00m\n\u001b[0;32m      5\u001b[0m data \u001b[38;5;241m=\u001b[39m pd\u001b[38;5;241m.\u001b[39mSeries([\u001b[38;5;241m1\u001b[39m, \u001b[38;5;241m1\u001b[39m, \u001b[38;5;241m5\u001b[39m, \u001b[38;5;241m5\u001b[39m, \u001b[38;5;241m1\u001b[39m, \u001b[38;5;241m1\u001b[39m, \u001b[38;5;241m1\u001b[39m])\n",
+      "File \u001b[1;32mc:\\Users\\jayke\\Desktop\\InterpreTS\\InterpreTS-1\\interpreTS\\core\\features\\feature_stability.py:3\u001b[0m\n\u001b[0;32m      1\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mpandas\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m \u001b[38;5;21;01mpd\u001b[39;00m\n\u001b[0;32m      2\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mnumpy\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m \u001b[38;5;21;01mnp\u001b[39;00m\n\u001b[1;32m----> 3\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mstatsmodels\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mtsa\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mstattools\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m acf\n\u001b[0;32m      4\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01minterpreTS\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mutils\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mdata_validation\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m validate_time_series_data\n\u001b[0;32m      6\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mcalculate_stability\u001b[39m(data, max_lag\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mNone\u001b[39;00m):\n",
+      "\u001b[1;31mModuleNotFoundError\u001b[0m: No module named 'statsmodels'"
+     ]
+    }
+   ],
+   "source": [
+    "sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname('__file__'), '../../../interpreTS')))\n",
+    "from core.features.feature_stability import calculate_stability\n",
+    "\n",
+    "data = pd.Series([1, 1, 2, 1, 1, 1, 2])\n",
+    "\n",
+    "stability = calculate_stability(data)\n",
+    "stability2 = calculate_stability(data, 3)\n",
+    "\n",
+    "print(stability, stability2)\n",
+    "\n",
+    "data2 = pd.Series([1, -8, 5, -2, 5, -4, 4])\n",
+    "\n",
+    "stability3 = calculate_stability(data2)\n",
+    "print(stability3)"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "env",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.10.1"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/examples/core/features/feature_trough.ipynb b/examples/core/features/feature_trough.ipynb
new file mode 100644
index 0000000..40964db
--- /dev/null
+++ b/examples/core/features/feature_trough.ipynb
@@ -0,0 +1,77 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import pandas as pd\n",
+    "from interpreTS import FeatureExtractor, Features, TimeSeriesData"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Troughs of time series: \n",
+      "0    -3\n",
+      "1    -5\n",
+      "2    -5\n",
+      "3   -10\n",
+      "Name: trough, dtype: int64\n",
+      "Troughs with params of time series: \n",
+      "0   -3\n",
+      "1    2\n",
+      "2    1\n",
+      "3   -5\n",
+      "4   -5\n",
+      "Name: trough, dtype: int64\n"
+     ]
+    }
+   ],
+   "source": [
+    "# Extract trough feature from time series data\n",
+    "raw_data = pd.DataFrame({'time': pd.date_range(start=\"2023-01-01\", periods=8, freq=\"D\"),\n",
+    "                         'value': [15, -3, 6, 2, 1, -5, 8, -10]})\n",
+    "ts_data = TimeSeriesData(raw_data.set_index('time'))\n",
+    "\n",
+    "extractor1 = FeatureExtractor(features=[Features.TROUGH])\n",
+    "feature1 = extractor1.extract_features(ts_data.data)\n",
+    "\n",
+    "extractor2 = FeatureExtractor(features=[Features.TROUGH], window_size=4,\n",
+    "                              feature_params={\"trough\": {\"start\": 1, \"end\": 3}})\n",
+    "feature2 = extractor2.extract_features(ts_data.data)\n",
+    "\n",
+    "print(f\"Troughs of time series: \\n{feature1[Features.TROUGH]}\")\n",
+    "print(f\"Troughs with params of time series: \\n{feature2[Features.TROUGH]}\")\n"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.10.1"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/examples/core/features/feature_variance.ipynb b/examples/core/features/feature_variance.ipynb
index 7351093..a8701c7 100644
--- a/examples/core/features/feature_variance.ipynb
+++ b/examples/core/features/feature_variance.ipynb
@@ -11,6 +11,16 @@
      "text": [
       "Variance of time series: 2.0\n"
      ]
+    },
+    {
+     "data": {
+      "text/plain": [
+       "2.5"
+      ]
+     },
+     "execution_count": 4,
+     "metadata": {},
+     "output_type": "execute_result"
     }
    ],
    "source": [
@@ -29,8 +39,16 @@
     "variance = calculate_variance(data)\n",
     "\n",
     "# Displaying the result\n",
-    "print(f\"Variance of time series: {variance}\")\n"
+    "print(f\"Variance of time series: {variance}\")\n",
+    "\n"
    ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
   }
  ],
  "metadata": {
diff --git a/examples/core/streaming_feature_extraction.ipynb b/examples/core/streaming_feature_extraction.ipynb
new file mode 100644
index 0000000..93bba7a
--- /dev/null
+++ b/examples/core/streaming_feature_extraction.ipynb
@@ -0,0 +1,106 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Note: you may need to restart the kernel to use updated packages.\n"
+     ]
+    },
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "ERROR: Invalid requirement: '#': Expected package name at the start of dependency specifier\n",
+      "    #\n",
+      "    ^\n"
+     ]
+    }
+   ],
+   "source": [
+    "pip install pandas # type: ignore"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {},
+   "outputs": [
+    {
+     "ename": "ModuleNotFoundError",
+     "evalue": "No module named 'interpreTS'",
+     "output_type": "error",
+     "traceback": [
+      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
+      "\u001b[1;31mModuleNotFoundError\u001b[0m                       Traceback (most recent call last)",
+      "Cell \u001b[1;32mIn[5], line 6\u001b[0m\n\u001b[0;32m      3\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mos\u001b[39;00m\n\u001b[0;32m      5\u001b[0m sys\u001b[38;5;241m.\u001b[39mpath\u001b[38;5;241m.\u001b[39minsert(\u001b[38;5;241m0\u001b[39m, os\u001b[38;5;241m.\u001b[39mpath\u001b[38;5;241m.\u001b[39mabspath(os\u001b[38;5;241m.\u001b[39mpath\u001b[38;5;241m.\u001b[39mjoin(os\u001b[38;5;241m.\u001b[39mpath\u001b[38;5;241m.\u001b[39mdirname(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m__file__\u001b[39m\u001b[38;5;124m'\u001b[39m), \u001b[38;5;124m'\u001b[39m\u001b[38;5;124m../../interpreTS\u001b[39m\u001b[38;5;124m'\u001b[39m)))\n\u001b[1;32m----> 6\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mcore\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mstreaming_feature_extractor\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m StreamingFeatureExtractor\n\u001b[0;32m      8\u001b[0m \u001b[38;5;66;03m# Initialize the StreamingFeatureExtractor\u001b[39;00m\n\u001b[0;32m      9\u001b[0m \u001b[38;5;66;03m# calculating 'mean', 'variance', and 'crossing_points' as an example\u001b[39;00m\n\u001b[0;32m     10\u001b[0m extractor \u001b[38;5;241m=\u001b[39m StreamingFeatureExtractor(features\u001b[38;5;241m=\u001b[39m[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mmean\u001b[39m\u001b[38;5;124m'\u001b[39m, \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mvariance\u001b[39m\u001b[38;5;124m'\u001b[39m, \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mcrossing_points\u001b[39m\u001b[38;5;124m'\u001b[39m], window_size\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m5\u001b[39m)\n",
+      "File \u001b[1;32mc:\\Users\\slawek\\Desktop\\Praca naukowa\\InterpreTS\\interpreTS\\core\\streaming_feature_extractor.py:4\u001b[0m\n\u001b[0;32m      1\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mpandas\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m \u001b[38;5;21;01mpd\u001b[39;00m\n\u001b[0;32m      2\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mnumpy\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m \u001b[38;5;21;01mnp\u001b[39;00m\n\u001b[1;32m----> 4\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mfeatures\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mfeature_crossing_points\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m calculate_crossing_points\n\u001b[0;32m      5\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mfeatures\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mfeature_spikeness\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m calculate_spikeness\n\u001b[0;32m      6\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mfeatures\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mfeature_peak\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m calculate_peak\n",
+      "File \u001b[1;32mc:\\Users\\slawek\\Desktop\\Praca naukowa\\InterpreTS\\interpreTS\\core\\features\\feature_crossing_points.py:3\u001b[0m\n\u001b[0;32m      1\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mnumpy\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m \u001b[38;5;21;01mnp\u001b[39;00m\n\u001b[0;32m      2\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mpandas\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m \u001b[38;5;21;01mpd\u001b[39;00m\n\u001b[1;32m----> 3\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01minterpreTS\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mutils\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mdata_validation\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m validate_time_series_data\n\u001b[0;32m      5\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mcalculate_crossing_points\u001b[39m(data):\n\u001b[0;32m      6\u001b[0m \u001b[38;5;250m    \u001b[39m\u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[0;32m      7\u001b[0m \u001b[38;5;124;03m    Calculate the number of times and the list of indices where the time series crosses its mean.\u001b[39;00m\n\u001b[0;32m      8\u001b[0m \u001b[38;5;124;03m    \u001b[39;00m\n\u001b[1;32m   (...)\u001b[0m\n\u001b[0;32m     19\u001b[0m \u001b[38;5;124;03m        - 'crossing_points': A list of indices where crossings occur.\u001b[39;00m\n\u001b[0;32m     20\u001b[0m \u001b[38;5;124;03m    \"\"\"\u001b[39;00m\n",
+      "\u001b[1;31mModuleNotFoundError\u001b[0m: No module named 'interpreTS'"
+     ]
+    }
+   ],
+   "source": [
+    "import pandas as pd\n",
+    "import sys\n",
+    "import os\n",
+    "\n",
+    "sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname('__file__'), '../../interpreTS')))\n",
+    "from core.streaming_feature_extractor import StreamingFeatureExtractor\n",
+    "\n",
+    "# Initialize the StreamingFeatureExtractor\n",
+    "# calculating 'mean', 'variance', and 'crossing_points' as an example\n",
+    "extractor = StreamingFeatureExtractor(features=['mean', 'variance', 'crossing_points'], window_size=5)\n",
+    "\n",
+    "# Simulate incoming data points (e.g., streaming data from a sensor)\n",
+    "data_points = [1, 2, 3, 2, 1, 4, 5, 6, 7, 6]\n",
+    "\n",
+    "# Processing each data point and print the extracted features when the window is full\n",
+    "print(\"Streaming Feature Extraction:\")\n",
+    "print(\"-\" * 50)\n",
+    "\n",
+    "for i, point in enumerate(data_points):\n",
+    "    print(f\"Adding data point: {point}\")\n",
+    "    result = extractor.add_data(point)\n",
+    "    \n",
+    "    if result is not None:\n",
+    "        print(f\"Features after processing point {i}:\")\n",
+    "        for feature_name, value in result.items():\n",
+    "            print(f\"  {feature_name}: {value}\")\n",
+    "        print(\"-\" * 50)\n",
+    "    else:\n",
+    "        print(\"  Not enough data for feature extraction yet.\")\n",
+    "        print(\"-\" * 50)\n",
+    "\n",
+    "print(\"Final Buffer State:\")\n",
+    "print(extractor.data_buffer)\n"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.9.13"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/interpreTS/__init__.py b/interpreTS/__init__.py
index 4d716d4..4f00d12 100644
--- a/interpreTS/__init__.py
+++ b/interpreTS/__init__.py
@@ -1,3 +1,4 @@
+from .core.streaming_feature_extractor import StreamingFeatureExtractor
 from .core.feature_extractor import FeatureExtractor
 from .core.feature_extractor import Features
 from .core.time_series_data import TimeSeriesData
diff --git a/interpreTS/core/feature_extractor.py b/interpreTS/core/feature_extractor.py
index f2cc130..1125b34 100644
--- a/interpreTS/core/feature_extractor.py
+++ b/interpreTS/core/feature_extractor.py
@@ -1,5 +1,8 @@
 import pandas as pd
 import numpy as np
+
+from .features.feature_binarize_mean import calculate_binarize_mean
+from .features.feature_crossing_points import calculate_crossing_points
 from .features.feature_spikeness import calculate_spikeness
 from .features.feature_peak import calculate_peak
 from .features.feature_trough import calculate_trough
@@ -8,6 +11,11 @@
 from .features.seasonality_strength import calculate_seasonality_strength
 from .features.feature_variance import calculate_variance
 from .features.feature_std_1st_der import calculate_std_1st_der
+from .features.feature_absolute_energy import absolute_energy
+from .features.feature_entropy import calculate_entropy
+from .features.feature_stability import calculate_stability
+from .features.feature_flat_spots import calculate_flat_spots
+from .features.feature_missing_points import missing_points
 
 class Features:
     LENGTH = 'length'
@@ -17,7 +25,14 @@ class Features:
     TROUGH = 'trough'
     VARIANCE = 'variance'
     SPIKENESS = 'spikeness'
+    ENTROPY = 'entropy'
     CALCULATE_SEASONALITY_STRENGTH = 'seasonality_strength'
+    ABSOLUTE_ENERGY = 'absolute_energy'
+    STABILITY = 'stability'
+    FLAT_SPOTS = 'flat_spots'
+    CROSSING_POINTS = 'crossing_points'
+    MISSING_POINTS = 'missing_points'
+    BINARIZE_MEAN = 'binarize_mean'
 
 class FeatureExtractor:
     def __init__(self, features=None, feature_params=None, window_size=5, stride=1, id_column=None, sort_column=None):
@@ -57,6 +72,13 @@ def __init__(self, features=None, feature_params=None, window_size=5, stride=1,
             Features.VARIANCE: calculate_variance,
             Features.SPIKENESS: calculate_spikeness,
             Features.CALCULATE_SEASONALITY_STRENGTH: calculate_seasonality_strength,
+            Features.ABSOLUTE_ENERGY: absolute_energy,
+            Features.ENTROPY: calculate_entropy,
+            Features.STABILITY: calculate_stability,
+            Features.FLAT_SPOTS: calculate_flat_spots, 
+            Features.CROSSING_POINTS: calculate_crossing_points, 
+            Features.MISSING_POINTS: missing_points,
+            Features.BINARIZE_MEAN: calculate_binarize_mean,
         }
 
     def extract_features(self, data):
diff --git a/interpreTS/core/features/distance_to_the_last_change_point.py b/interpreTS/core/features/distance_to_the_last_change_point.py
new file mode 100644
index 0000000..9afdd75
--- /dev/null
+++ b/interpreTS/core/features/distance_to_the_last_change_point.py
@@ -0,0 +1,69 @@
+import pandas as pd
+import numpy as np
+from interpreTS.utils.data_validation import validate_time_series_data
+
+def calculate_distance_to_last_trend_change(data, window_size=5):
+    """
+    Calculate the distance (in terms of indices) to the last trend change point in a time series.
+
+    Parameters
+    ----------
+    data : pd.Series or np.ndarray
+        The time series data for which the distance to the last trend change is to be calculated.
+    window_size : int, optional
+        The size of the rolling window to calculate mean (default is 5).
+
+    Returns
+    -------
+    int
+        The distance (in terms of indices) to the last trend change point.
+        If no change is detected, returns None.
+
+    Raises
+    ------
+    TypeError
+        If the data is not a valid time series type.
+    ValueError
+        If the data contains NaN values or is too short to calculate mean.
+
+    Examples
+    --------
+    >>> import pandas as pd
+    >>> data = pd.Series([1, 2, 3, 4, 5, 6, 7, 6, 5, 4, 3, 2, 1])
+    >>> calculate_distance_to_last_trend_change(data, window_size=3)
+    4
+    """
+    # Validate the time series data
+    validate_time_series_data(data, require_datetime_index=False)
+
+    # Handle empty or insufficient data
+    if len(data) < window_size + 1:
+        raise ValueError("The time series is too short for the specified rolling window size.")
+    
+    # Convert data to a pandas Series if it's an ndarray
+    if isinstance(data, np.ndarray):
+        data = pd.Series(data)
+    
+    # Check for monotonic data
+    if data.is_monotonic_increasing or data.is_monotonic_decreasing:
+        return None  # No trend change can occur in monotonic data
+    
+    # Calculate rolling mean
+    rolling_mean = data.rolling(window=window_size).mean()
+
+    # Calculate the change in mean as the first difference of rolling means
+    change_in_mean = rolling_mean.diff()
+
+    # Detect trend change points: where the direction of the change in mean flips
+    trend_changes = (change_in_mean > 0) != (change_in_mean.shift(1) > 0)
+
+    # Find the last trend change point
+    last_change_point = trend_changes.idxmax() if trend_changes.any() else None
+    
+    if last_change_point is None:
+        return None  # No trend change detected
+
+    # Calculate the distance to the last trend change point
+    distance_to_last_change = len(data) - last_change_point
+
+    return distance_to_last_change
\ No newline at end of file
diff --git a/interpreTS/core/features/feature_absolute_energy.py b/interpreTS/core/features/feature_absolute_energy.py
new file mode 100644
index 0000000..cf3b89d
--- /dev/null
+++ b/interpreTS/core/features/feature_absolute_energy.py
@@ -0,0 +1,53 @@
+import pandas as pd
+import numpy as np
+from interpreTS.utils.data_validation import validate_time_series_data
+
+def absolute_energy(data, start=None, end=None):
+    """
+    Calculate the absolute energy of a time series within an optional range.
+
+    Absolute energy is defined as the sum of squared values in the time series.
+
+    Parameters
+    ----------
+    data : pd.Series or np.ndarray
+        The time series data for which the absolute energy is to be calculated.
+    start : int, str, or None, optional
+        The starting index, timestamp, or position for slicing the data.
+        If None, the series starts from the beginning.
+    end : int, str, or None, optional
+        The ending index, timestamp, or position for slicing the data.
+        If None, the series ends at the last value.
+
+    Returns
+    -------
+    float
+        The absolute energy of the specified range in the provided time series.
+
+    Raises
+    ------
+    TypeError
+        If the data is not a valid time series type.
+    ValueError
+        If the data contains NaN values.
+
+    Examples
+    --------
+    >>> import pandas as pd
+    >>> data = pd.Series([1, 2, 3, 4])
+    >>> calculate_absolute_energy(data)
+    30.0
+    >>> calculate_absolute_energy(data, start=1, end=3)
+    13.0
+    """
+
+    # Validate the time series without requiring a DateTime index
+    validate_time_series_data(data, require_datetime_index=False)
+
+    # Slice the data based on start and end, if provided
+    if start is not None or end is not None:
+        data = data[start:end]
+
+    # Calculate and return the absolute energy, handling empty series by returning NaN
+    return np.sum(np.square(data)) if len(data) > 0 else np.nan
+
diff --git a/interpreTS/core/features/feature_binarize_mean.py b/interpreTS/core/features/feature_binarize_mean.py
new file mode 100644
index 0000000..a72f562
--- /dev/null
+++ b/interpreTS/core/features/feature_binarize_mean.py
@@ -0,0 +1,34 @@
+import pandas as pd
+import numpy as np
+from interpreTS.utils.data_validation import validate_time_series_data
+
+def calculate_binarize_mean(data):
+    """
+    Calculate the binarize mean of a time series.
+    Parameters
+    ----------
+    data : pd.Series or np.ndarray
+        The time series data for which the binarize mean is to be calculated.
+    Returns
+    -------
+    float
+        The binarize mean of the provided time series.
+    Raises
+    ------
+    TypeError
+        If the data is not a valid time series type.
+    ValueError
+        If the data contains NaN values.
+    Examples
+    --------
+    >>> import pandas as pd
+    >>> data = pd.Series([1, 2, 3, 4, 5])
+    >>> calculate_binarize_mean(data)
+    0.6
+    """
+    validate_time_series_data(data, require_datetime_index=False)
+
+    mean_value = data.mean()
+    binarized_data = (data > mean_value).astype(int)
+
+    return binarized_data.mean() if isinstance(data, pd.Series) and len(data) > 0 else np.nan
diff --git a/interpreTS/core/features/feature_crossing_points.py b/interpreTS/core/features/feature_crossing_points.py
new file mode 100644
index 0000000..55573b4
--- /dev/null
+++ b/interpreTS/core/features/feature_crossing_points.py
@@ -0,0 +1,44 @@
+import numpy as np
+import pandas as pd
+from interpreTS.utils.data_validation import validate_time_series_data
+
+def calculate_crossing_points(data):
+    """
+    Calculate the number of times and the list of indices where the time series crosses its mean.
+    
+    Parameters
+    ----------
+    data : pd.Series or np.ndarray
+        The time series data for which mean crossings are to be calculated.
+    
+    Returns
+    -------
+    dict
+        A dictionary containing:
+        - 'crossing_count': The total number of crossings.
+        - 'crossing_points': A list of indices where crossings occur.
+    """
+    validate_time_series_data(data)
+
+    # Convert to numpy array if data is a pandas Series
+    if isinstance(data, pd.Series):
+        data = data.to_numpy()
+
+    # Return immediately if data is empty
+    if len(data) == 0:
+        return {'crossing_count': 0, 'crossing_points': []}
+
+    mean_value = np.mean(data)
+
+    # Return 0 crossings if all values are above or below the mean
+    if np.all(data <= mean_value) or np.all(data >= mean_value):
+        return {'crossing_count': 0, 'crossing_points': []}
+
+    # Calculate crossings
+    above_mean = data > mean_value
+    crossings = np.where(np.diff(above_mean.astype(int)) != 0)[0] 
+
+    return {
+        'crossing_count': len(crossings),
+        'crossing_points': list(crossings)
+    }
diff --git a/interpreTS/core/features/feature_entropy.py b/interpreTS/core/features/feature_entropy.py
new file mode 100644
index 0000000..bda12c5
--- /dev/null
+++ b/interpreTS/core/features/feature_entropy.py
@@ -0,0 +1,70 @@
+import pandas as pd
+import numpy as np
+from interpreTS.utils.data_validation import validate_time_series_data
+
+def calculate_entropy(data, bins=None):
+    """
+    Calculate the entropy of a time series based on its value distribution.
+
+    Parameters
+    ----------
+    data : pd.Series or np.ndarray
+        The time series data for which the entropy is to be calculated.
+    bins : int, optional
+        The number of bins to use for discretizing the data. 
+        Default is 10.
+
+    Returns
+    -------
+    float
+        The normalized entropy, ranging from 0 to 1, where 0 indicates 
+        no entropy (completely predictable) and 1 indicates maximum entropy 
+        (completely random).
+
+    Raises
+    ------
+    TypeError
+        If the data is not a valid time series type.
+    ValueError
+        If the data contains NaN values or is too short to calculate entropy.
+    """
+    # Validate the time series data
+    validate_time_series_data(data, require_datetime_index=False)
+    
+    # Convert data to numpy array if it's a pandas Series
+    if isinstance(data, pd.Series):
+        data = data.values
+        
+    # Handle empty or insufficient data
+    if len(data) == 0:
+        raise ValueError("The input data is empty.")
+    if len(data) < 2:
+        raise ValueError("The input data is too short to calculate entropy.")
+    
+    if bins is None:
+        bins = len(data)
+    
+    # Check for constant values
+    if np.all(data == data[0]):
+        return 0.0  # No entropy for constant values
+    
+    # Discretize the data into bins and get the histogram
+    hist, _ = np.histogram(data, bins=bins, density=True)
+    
+    # Normalize histogram to get a probability distribution
+    probabilities = hist / np.sum(hist)
+    
+    # Filter out zero probabilities to avoid log(0)
+    probabilities = probabilities[probabilities > 0]
+    
+    # Calculate Shannon entropy manually
+    shannon_entropy = -np.sum(probabilities * np.log2(probabilities))
+    
+    # Normalize the entropy to range [0, 1]
+    max_entropy = np.log2(len(probabilities))
+    normalized_entropy = shannon_entropy / max_entropy if max_entropy > 0 else 0.0
+    
+    # Ensure the result is within [0, 1]
+    normalized_entropy = max(0.0, min(normalized_entropy, 1.0))
+    
+    return normalized_entropy
\ No newline at end of file
diff --git a/interpreTS/core/features/feature_flat_spots.py b/interpreTS/core/features/feature_flat_spots.py
new file mode 100644
index 0000000..037f16e
--- /dev/null
+++ b/interpreTS/core/features/feature_flat_spots.py
@@ -0,0 +1,44 @@
+from itertools import groupby
+
+import numpy as np
+import pandas as pd
+
+from interpreTS.utils.data_validation import validate_time_series_data
+
+def calculate_flat_spots(data, window_size=5):
+    """
+    Calculate the number of flat spots in the time series.
+    
+    Flat spots are defined as maximum run-lengths across equally-sized segments of the time series.
+    
+    Parameters
+    ----------
+    data : pd.Series or np.ndarray
+        The time series data for which flat spots are to be calculated.
+    window_size : int, optional
+        The size of the window to look for flat spots (default is 5).
+
+    Returns
+    -------
+    int
+        The number of flat spots in the time series.
+    
+    Examples
+    --------
+    >>> import pandas as pd
+    >>> data = pd.Series([1, 1, 1, 2, 3, 3, 4, 4, 4, 4, 4, 5, 1, 1])
+    >>> calculate_flat_spots(data)
+    4
+    """
+
+    # Validate the time series without requiring a DateTime index
+    validate_time_series_data(data, require_datetime_index=False)
+
+    run_lengths = [
+        len(list(group))
+        for i in range(0, len(data), window_size)
+        for _, group in groupby(data[i:i + window_size])
+    ]
+    max_flat_spot = max(run_lengths, default=0)
+
+    return max_flat_spot
\ No newline at end of file
diff --git a/interpreTS/core/features/feature_missing_points.py b/interpreTS/core/features/feature_missing_points.py
new file mode 100644
index 0000000..6b6bc9e
--- /dev/null
+++ b/interpreTS/core/features/feature_missing_points.py
@@ -0,0 +1,48 @@
+import pandas as pd
+import numpy as np
+
+def missing_points(data, percentage=True):
+    """
+    Calculate the percentage or count of missing (NaN or None) values in a time series.
+
+    Parameters
+    ----------
+    data : pd.Series or np.ndarray
+        The time series data for which missing information is to be calculated.
+    percentage : bool, optional
+        If True, returns the percentage of missing values.
+        If False, returns the count of missing values.
+        Default is True.
+
+    Returns
+    -------
+    float or int
+        The percentage or count of missing values in the provided time series.
+
+    Raises
+    ------
+    TypeError
+        If the data is not a valid time series type.
+
+    Examples
+    --------
+    >>> import pandas as pd
+    >>> data = pd.Series([1, 2, np.nan, 4, None])
+    >>> missing_points(data)
+    40.0
+    >>> missing_points(data, percentage=False)
+    2
+    """
+
+    # Convert to a pandas Series if data is an ndarray
+    if isinstance(data, np.ndarray):
+        data = pd.Series(data)
+
+    # Calculate total and missing values
+    total_values = len(data)
+    missing_values = data.isna().sum()
+
+    # Return either percentage or count of missing values
+    if total_values == 0:
+        return np.nan
+    return missing_values / total_values if percentage else missing_values
\ No newline at end of file
diff --git a/interpreTS/core/features/feature_peak.py b/interpreTS/core/features/feature_peak.py
index 0650ed7..c33aae1 100644
--- a/interpreTS/core/features/feature_peak.py
+++ b/interpreTS/core/features/feature_peak.py
@@ -44,8 +44,11 @@ def calculate_peak(data, start=None, end=None):
     validate_time_series_data(data, require_datetime_index=False)
 
     # Slice the data based on start and end, if provided
-    if start is not None or end is not None:
-        data = data[start:end]
-
+    if end is None:
+        end = len(data)
+    if start is None:
+        start = 0
+    data = data[start:end]
+    
     # Calculate and return the maximum, handling empty series by returning NaN
-    return data.max() if len(data) > 0 else np.nan
+    return data.max() if len(data) > 0 else np.nan
\ No newline at end of file
diff --git a/interpreTS/core/features/feature_stability.py b/interpreTS/core/features/feature_stability.py
new file mode 100644
index 0000000..ab84d77
--- /dev/null
+++ b/interpreTS/core/features/feature_stability.py
@@ -0,0 +1,60 @@
+import pandas as pd
+import numpy as np
+from statsmodels.tsa.stattools import acf
+from interpreTS.utils.data_validation import validate_time_series_data
+
+def calculate_stability(data, max_lag=None):
+    """
+    Calculate the stability of a time series based on autocorrelation.
+
+    Parameters
+    ----------
+    data : pd.Series or np.ndarray
+        The time series data for which the stability is to be calculated.
+    max_lag : int, optional
+        The maximum number of lags to consider for autocorrelation. 
+        If None, it will be set to `min(12, len(data) - 1)`.
+
+    Returns
+    -------
+    float
+        The stability strength, ranging from 0 to 1, where 1 indicates high stability.
+
+    Raises
+    ------
+    TypeError
+        If the data is not a valid time series type.
+    ValueError
+        If the data contains NaN values or is too short to calculate stability.
+
+    """
+    # Validate the time series data
+    validate_time_series_data(data, require_datetime_index=False)
+    
+    # Dynamically determine max_lag if not provided
+    if max_lag is None:
+        max_lag = min(12, len(data) - 1)
+    
+    # Handle empty or insufficient data
+    if len(data) < max_lag + 1 or max_lag < 1:
+        return np.nan
+    
+    # Check for constant values
+    if data.nunique() == 1:
+        return 1.0  # Perfect stability for constant values
+    
+    # Calculate the autocorrelation of the data up to the max lag
+    autocorr_values = acf(data, nlags=max_lag, fft=True)
+    
+    # Exclude the first autocorrelation (lag 0) as it is always 1
+    autocorr_values = autocorr_values[1:]
+    
+    # Calculate stability based on variance of autocorrelation values at higher lags
+    mean_autocorr = np.mean(np.abs(autocorr_values))
+    variance_autocorr = np.var(autocorr_values)
+    
+    # Combine mean and variance to get a measure of stability
+    stability_strength = 1 - (mean_autocorr + variance_autocorr) / 2
+    stability_strength = max(0, min(stability_strength, 1))  # Ensure result is within [0, 1]
+    
+    return stability_strength
diff --git a/interpreTS/core/features/feature_trough.py b/interpreTS/core/features/feature_trough.py
index c34d600..fa20541 100644
--- a/interpreTS/core/features/feature_trough.py
+++ b/interpreTS/core/features/feature_trough.py
@@ -44,8 +44,11 @@ def calculate_trough(data, start=None, end=None):
     validate_time_series_data(data, require_datetime_index=False)
 
     # Slice the data based on start and end, if provided
-    if start is not None or end is not None:
-        data = data[start:end]
-
+    if end is None:
+        end = len(data)
+    if start is None:
+        start = 0
+    data = data[start:end]
+    
     # Calculate and return the minimum, handling empty series by returning NaN
-    return data.min() if len(data) > 0 else np.nan
+    return data.min() if len(data) > 0 else np.nan
\ No newline at end of file
diff --git a/interpreTS/core/features/feature_variance.py b/interpreTS/core/features/feature_variance.py
index e8daa5a..9f7126b 100644
--- a/interpreTS/core/features/feature_variance.py
+++ b/interpreTS/core/features/feature_variance.py
@@ -2,7 +2,7 @@
 import numpy as np
 from interpreTS.utils.data_validation import validate_time_series_data
 
-def calculate_variance(data, ddof=0):
+def calculate_variance(data, ddof=1):
     """
     Calculate the variance value of a time series with specified degrees of freedom.
     
@@ -11,7 +11,8 @@ def calculate_variance(data, ddof=0):
     data : pd.Series or np.ndarray
         The time series data for which the variance is to be calculated.
     ddof : int, optional
-        The degrees of freedom to use when calculating the variance. Default is 0 (population variance).
+        Delta degrees of freedom. The divisor used in calculations is N - ddof, where N is the number of elements. 
+        A ddof of 1 provides the sample variance, and a ddof of 0 provides the population variance. Default is 1.
         
     Returns
     -------
@@ -37,5 +38,9 @@ def calculate_variance(data, ddof=0):
     # Validate the time series without requiring a DateTime index
     validate_time_series_data(data, require_datetime_index=False)
     
+    # Handle the case where the series has only one value
+    if len(data) == 1:
+        return 0.0
+
     # Calculate and return the variance with specified ddof, handling empty series by returning NaN
     return np.var(data, ddof=ddof) if len(data) > 0 else np.nan
\ No newline at end of file
diff --git a/interpreTS/core/features/mean_change.py b/interpreTS/core/features/mean_change.py
new file mode 100644
index 0000000..30afca9
--- /dev/null
+++ b/interpreTS/core/features/mean_change.py
@@ -0,0 +1,62 @@
+import pandas as pd
+import numpy as np
+from interpreTS.utils.data_validation import validate_time_series_data
+
+def calculate_mean_change(data, window_size=5):
+    """
+    Calculate the change in mean over time in a time series.
+
+    Parameters
+    ----------
+    data : pd.Series or np.ndarray
+        The time series data for which the change in mean is to be calculated.
+    window_size : int, optional
+        The size of the rolling window to calculate mean (default is 5).
+
+    Returns
+    -------
+    pd.Series
+        A series containing the change in mean over time, with the same index as the input.
+
+    Raises
+    ------
+    TypeError
+        If the data is not a valid time series type.
+    ValueError
+        If the data contains NaN values or is too short to calculate mean.
+
+    Examples
+    --------
+    >>> import pandas as pd
+    >>> data = pd.Series([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+    >>> calculate_mean_change(data, window_size=3)
+    0     NaN
+    1     NaN
+    2     NaN
+    3     1.00
+    4     1.00
+    5     1.00
+    6     1.00
+    7     1.00
+    8     1.00
+    9     1.00
+    dtype: float64
+    """
+    # Validate the time series data
+    validate_time_series_data(data, require_datetime_index=False)
+    
+    # Handle empty or insufficient data
+    if len(data) < window_size + 1:
+        raise ValueError("The time series is too short for the specified rolling window size.")
+    
+    # Convert data to a pandas Series if it's an ndarray
+    if isinstance(data, np.ndarray):
+        data = pd.Series(data)
+
+    # Calculate rolling mean
+    rolling_mean = data.rolling(window=window_size).mean()
+
+    # Calculate the change in mean as the first difference of rolling means
+    change_in_mean = rolling_mean.diff()
+
+    return change_in_mean
\ No newline at end of file
diff --git a/interpreTS/core/features/variability_in_sub_periods.py b/interpreTS/core/features/variability_in_sub_periods.py
new file mode 100644
index 0000000..151a146
--- /dev/null
+++ b/interpreTS/core/features/variability_in_sub_periods.py
@@ -0,0 +1,66 @@
+import pandas as pd
+import numpy as np
+from interpreTS.utils.data_validation import validate_time_series_data
+
+def calculate_variability_in_sub_periods(data, window_size, step_size=None, ddof=0):
+    """
+    Calculate the variance within sub-periods of a time series, providing a measure of variability.
+    
+    Parameters
+    ----------
+    data : pd.Series or np.ndarray
+        The time series data for which the variability is to be calculated.
+    window_size : int
+        The size of each sub-period window (number of points in each window).
+    step_size : int, optional
+        The step size between sub-periods. If None, it defaults to window_size (non-overlapping windows).
+    ddof : int, optional
+        The degrees of freedom to use when calculating variance within each sub-period. Default is 0 (population variance).
+        
+    Returns
+    -------
+    pd.Series
+        A series of variance values representing the variability in each sub-period.
+        
+    Raises
+    ------
+    TypeError
+        If the data is not a valid time series type.
+    ValueError
+        If the data contains NaN values or if window_size is larger than the data length.
+        
+    Examples
+    --------
+    >>> import pandas as pd
+    >>> data = pd.Series([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+    >>> calculate_variability_in_sub_periods(data, window_size=5)
+    0    2.5
+    1    2.5
+    dtype: float64
+    """
+    # Validate the time series data without requiring a DateTime index
+    validate_time_series_data(data, require_datetime_index=False)
+    
+    # Convert numpy array to pandas Series if necessary
+    if isinstance(data, np.ndarray):
+        data = pd.Series(data)
+    
+    # Handle edge cases
+    if len(data) < window_size:
+        raise ValueError("Window size must be smaller than or equal to the length of the data.")
+    
+    if step_size is None:
+        step_size = window_size  # Default to non-overlapping windows
+
+    # Initialize a list to hold variability values
+    variability_measures = []
+
+    # Calculate variance for each sub-period
+    for start in range(0, len(data) - window_size + 1, step_size):
+        sub_period = data[start:start + window_size]
+        
+        # Append the calculated variance for the sub-period
+        variability_measures.append(np.var(sub_period, ddof=ddof))
+    
+    # Return as a Pandas Series for easier handling of the result
+    return pd.Series(variability_measures)
\ No newline at end of file
diff --git a/interpreTS/core/features/variance_change.py b/interpreTS/core/features/variance_change.py
new file mode 100644
index 0000000..361865c
--- /dev/null
+++ b/interpreTS/core/features/variance_change.py
@@ -0,0 +1,62 @@
+import pandas as pd
+import numpy as np
+from interpreTS.utils.data_validation import validate_time_series_data
+
+def calculate_change_in_variance(data, window_size=5):
+    """
+    Calculate the change in variance over time in a time series.
+
+    Parameters
+    ----------
+    data : pd.Series or np.ndarray
+        The time series data for which the change in variance is to be calculated.
+    window_size : int, optional
+        The size of the rolling window to calculate variance (default is 5).
+
+    Returns
+    -------
+    pd.Series
+        A series containing the change in variance over time, with the same index as the input.
+
+    Raises
+    ------
+    TypeError
+        If the data is not a valid time series type.
+    ValueError
+        If the data contains NaN values or is too short to calculate variance.
+
+    Examples
+    --------
+    >>> import pandas as pd
+    >>> data = pd.Series([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+    >>> calculate_change_in_variance(data, window_size=3)
+    0     NaN
+    1     NaN
+    2     NaN
+    3     0.00
+    4     0.00
+    5     0.00
+    6     0.00
+    7     0.00
+    8     0.00
+    9     0.00
+    dtype: float64
+    """
+    # Validate the time series data
+    validate_time_series_data(data, require_datetime_index=False)
+    
+    # Handle empty or insufficient data
+    if len(data) < window_size + 1:
+        raise ValueError("The time series is too short for the specified rolling window size.")
+    
+    # Convert data to a pandas Series if it's an ndarray
+    if isinstance(data, np.ndarray):
+        data = pd.Series(data)
+
+    # Calculate rolling variance
+    rolling_variance = data.rolling(window=window_size).var()
+
+    # Calculate the change in variance as the first difference of rolling variances
+    change_in_variance = rolling_variance.diff()
+
+    return change_in_variance
\ No newline at end of file
diff --git a/interpreTS/core/streaming_feature_extractor.py b/interpreTS/core/streaming_feature_extractor.py
new file mode 100644
index 0000000..258688a
--- /dev/null
+++ b/interpreTS/core/streaming_feature_extractor.py
@@ -0,0 +1,99 @@
+import pandas as pd
+import numpy as np
+
+from .features.feature_crossing_points import calculate_crossing_points
+from .features.feature_spikeness import calculate_spikeness
+from .features.feature_peak import calculate_peak
+from .features.feature_trough import calculate_trough
+from .features.feature_length import calculate_length
+from .features.feature_mean import calculate_mean
+from .features.seasonality_strength import calculate_seasonality_strength
+from .features.feature_variance import calculate_variance
+from .features.feature_std_1st_der import calculate_std_1st_der
+from .features.feature_flat_spots import calculate_flat_spots
+
+class StreamingFeatureExtractor:
+    def __init__(self, features=None, feature_params=None, window_size=5):
+        """
+        Initialize the StreamingFeatureExtractor with a list of features to calculate and optional parameters.
+        
+        Parameters
+        ----------
+        features : list of str, optional
+            A list of features to calculate. Default is None, which calculates all available features.
+        feature_params : dict, optional
+            Parameters for specific features, where keys are feature names and values are dicts of parameters.
+        window_size : int, optional
+            The size of the window for feature extraction.
+        """
+        self.features = features if features is not None else [
+            'length', 'mean', 'peak', 'std_1st_der', 'trough', 
+            'variance', 'spikeness', 'seasonality_strength', 'flat_spots', 
+            'crossing_points'
+        ]
+        self.feature_params = feature_params if feature_params is not None else {}
+        self.window_size = window_size
+        self.data_buffer = []
+
+        # Map of feature names to calculation functions
+        self.feature_functions = {
+            'length': calculate_length,
+            'mean': calculate_mean,
+            'peak': calculate_peak,
+            'std_1st_der': calculate_std_1st_der,
+            'trough': calculate_trough,
+            'variance': calculate_variance,
+            'spikeness': calculate_spikeness,
+            'seasonality_strength': calculate_seasonality_strength,
+            'flat_spots': calculate_flat_spots,
+            'crossing_points': calculate_crossing_points,
+        }
+
+    def add_data(self, new_data):
+        """
+        Add new data to the buffer and calculate features if the window size is met.
+        
+        Parameters
+        ----------
+        new_data : float or int
+            New data point to add to the buffer.
+            
+        Returns
+        -------
+        dict or None
+            A dictionary of calculated features if the window size is met, else None.
+        """
+        self.data_buffer.append(new_data)
+
+        # Sprawdzanie długości bufora przed obliczaniem cech
+        if len(self.data_buffer) < self.window_size:
+            return None
+
+        window_data = pd.Series(self.data_buffer[-self.window_size:])
+        features = self._calculate_features(window_data)
+        return features
+
+
+    def _calculate_features(self, data):
+        """
+        Calculate selected features on the current window data.
+        
+        Parameters
+        ----------
+        data : pd.Series
+            The data window on which to calculate features.
+            
+        Returns
+        -------
+        dict
+            A dictionary containing calculated features.
+        """
+        extracted_features = {}
+
+        for feature_name in self.features:
+            if feature_name in self.feature_functions:
+                # Retrieve parameters for each feature, if provided
+                params = self.feature_params.get(feature_name, {})
+                extracted_features[feature_name] = self.feature_functions[feature_name](data, **params)
+
+        return extracted_features


Test patch:
diff --git a/tests/core/features/test_absolute_energy.py b/tests/core/features/test_absolute_energy.py
new file mode 100644
index 0000000..fcc9ffe
--- /dev/null
+++ b/tests/core/features/test_absolute_energy.py
@@ -0,0 +1,75 @@
+import pytest
+import pandas as pd
+import numpy as np
+from interpreTS.core.features.feature_absolute_energy import absolute_energy
+
+def test_absolute_energy():
+    """
+    Test that absolute_energy correctly calculates the absolute energy of a time series.
+    """
+    data = pd.Series([1, 2, 3, 4, 5])
+    assert absolute_energy(data) == 55.0, "The absolute energy should be 55.0"
+
+def test_absolute_energy_empty_series():
+    """
+    Test that absolute_energy returns NaN for an empty time series.
+    """
+    data = pd.Series([])
+    assert pd.isna(absolute_energy(data)), "The absolute energy of an empty series should be NaN"
+
+def test_absolute_energy_numpy_array():
+    """
+    Test that absolute_energy correctly calculates the absolute energy of a numpy array.
+    """
+    data = np.array([1, 2, 3, 4, 5])
+    assert absolute_energy(data) == 55.0, "The absolute energy should be 55.0 for numpy array"
+
+def test_absolute_energy_with_negative_values():
+    """
+    Test that absolute_energy correctly calculates the absolute energy for a series with negative values.
+    """
+    data = pd.Series([-1, -2, -3, -4, -5])
+    assert absolute_energy(data) == 55.0, "The absolute energy should be 55.0 even with negative values"
+
+def test_absolute_energy_with_start_and_end():
+    """
+    Test that absolute_energy correctly calculates the absolute energy within a specified start and end range.
+    """
+    data = pd.Series([1, 2, 3, 4, 5])
+    assert absolute_energy(data, start=1, end=4) == 29.0, "The absolute energy within index range 1 to 4 should be 29.0"
+
+def test_absolute_energy_with_out_of_range_indices():
+    """
+    Test that absolute_energy handles out-of-range start and end indices gracefully.
+    """
+    data = pd.Series([1, 2, 3, 4, 5])
+    assert pd.isna(absolute_energy(data, start=10)), "The absolute energy with out-of-range start index should be NaN"
+    assert pd.isna(absolute_energy(data, end=-10)), "The absolute energy with out-of-range end index should be NaN"
+
+def test_absolute_energy_with_start_greater_than_end():
+    """
+    Test that absolute_energy returns NaN if start index is greater than end index.
+    """
+    data = pd.Series([1, 2, 3, 4, 5])
+    assert pd.isna(absolute_energy(data, start=4, end=2)), "The absolute energy should be NaN if start index is greater than end index"
+
+def test_absolute_energy_with_all_zeros():
+    """
+    Test that absolute_energy correctly handles a series of all zeros.
+    """
+    data = pd.Series([0, 0, 0, 0, 0])
+    assert absolute_energy(data) == 0.0, "The absolute energy of a series of all zeros should be 0.0"
+
+def test_absolute_energy_with_single_element():
+    """
+    Test that absolute_energy correctly handles a series with a single element.
+    """
+    data = pd.Series([42])
+    assert absolute_energy(data) == 42**2, "The absolute energy of a single-element series should be the square of the element"
+
+def test_absolute_energy_with_floats():
+    """
+    Test that absolute_energy handles float values correctly.
+    """
+    data = pd.Series([1.5, 2.5, 3.5])
+    assert absolute_energy(data) == 1.5**2 + 2.5**2 + 3.5**2, "The absolute energy should correctly sum the squares of float values"
diff --git a/tests/core/features/test_distance_to_the_last_change_point.py b/tests/core/features/test_distance_to_the_last_change_point.py
new file mode 100644
index 0000000..6902d95
--- /dev/null
+++ b/tests/core/features/test_distance_to_the_last_change_point.py
@@ -0,0 +1,111 @@
+import pytest
+import pandas as pd
+import numpy as np
+from interpreTS.core.features.distance_to_the_last_change_point import calculate_distance_to_last_trend_change  
+
+def test_calculate_distance_to_last_trend_change_constant_data():
+    """
+    Test that calculate_distance_to_last_trend_change returns None for constant data (no trend change).
+    """
+    data = pd.Series([5] * 10)  # Constant series
+    result = calculate_distance_to_last_trend_change(data, window_size=3)
+    assert result is None, "There should be no trend change in constant data"
+
+
+def test_calculate_distance_to_last_trend_change_increasing_data():
+    """
+    Test that calculate_distance_to_last_trend_change detects the correct distance for increasing data.
+    """
+    data = pd.Series([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])  # Increasing trend
+    result = calculate_distance_to_last_trend_change(data, window_size=3)
+    assert result is None, "No trend change should occur for consistently increasing data"
+
+
+def test_calculate_distance_to_last_trend_change_decreasing_data():
+    """
+    Test that calculate_distance_to_last_trend_change detects the correct distance for decreasing data.
+    """
+    data = pd.Series([10, 9, 8, 7, 6, 5, 4, 3, 2, 1])  # Decreasing trend
+    result = calculate_distance_to_last_trend_change(data, window_size=3)
+    assert result is None, "No trend change should occur for consistently decreasing data"
+
+
+def test_calculate_distance_to_last_trend_change_random_data():
+    """
+    Test that calculate_distance_to_last_trend_change handles random data and detects the last trend change.
+    """
+    np.random.seed(0)
+    data = pd.Series(np.random.randn(100))  # Random normal data
+    result = calculate_distance_to_last_trend_change(data, window_size=5)
+    assert isinstance(result, int), "The result should be an integer representing the distance"
+    assert result >= 0, "Distance to last trend change should be a non-negative integer"
+
+
+def test_calculate_distance_to_last_trend_change_no_trend_change():
+    """
+    Test that calculate_distance_to_last_trend_change returns None when there is no trend change.
+    """
+    data = pd.Series([1, 1, 1, 1, 1, 1, 1, 1, 1, 1])  # Constant data
+    result = calculate_distance_to_last_trend_change(data, window_size=3)
+    assert result is None, "There should be no trend change in constant data"
+
+
+def test_calculate_distance_to_last_trend_change_single_trend_change():
+    """
+    Test that calculate_distance_to_last_trend_change detects the correct distance to the last trend change.
+    """
+    data = pd.Series([1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1])  # Trend change after index 5
+    result = calculate_distance_to_last_trend_change(data, window_size=3)
+    assert result == 8, "Distance to the last trend change should be 8"
+
+
+def test_calculate_distance_to_last_trend_change_multiple_trend_changes():
+    """
+    Test that calculate_distance_to_last_trend_change detects the correct distance to the last trend change 
+    after multiple changes.
+    """
+    data = pd.Series([1, 2, 3, 4, 5, 6, 7, 6, 5, 4, 5, 6, 7])  # Multiple trend changes
+    result = calculate_distance_to_last_trend_change(data, window_size=3)
+    assert result == 10, "Distance to the last trend change should be 10 (last change is at index 10)"
+
+
+def test_calculate_distance_to_last_trend_change_empty_series():
+    """
+    Test that calculate_distance_to_last_trend_change raises an error for an empty series.
+    """
+    data = pd.Series([])
+    with pytest.raises(ValueError, match="too short"):
+        calculate_distance_to_last_trend_change(data, window_size=3)
+
+
+def test_calculate_distance_to_last_trend_change_insufficient_data():
+    """
+    Test that calculate_distance_to_last_trend_change raises an error for insufficient data.
+    """
+    data = pd.Series([1, 2])
+    with pytest.raises(ValueError, match="too short"):
+        calculate_distance_to_last_trend_change(data, window_size=3)
+
+
+def test_calculate_distance_to_last_trend_change_window_size():
+    """
+    Test that calculate_distance_to_last_trend_change respects the specified window size.
+    """
+    data = pd.Series([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])  # Increasing trend
+    window_size = 4
+    result = calculate_distance_to_last_trend_change(data, window_size=window_size)
+    if data.is_monotonic_increasing or data.is_monotonic_decreasing:
+        assert result is None, "For monotonic data, result should be None"
+    else:
+        assert isinstance(result, int), "Result should be an integer representing the distance to the last trend change"
+
+
+
+def test_calculate_distance_to_last_trend_change_numpy_array():
+    """
+    Test that calculate_distance_to_last_trend_change works with a numpy array.
+    """
+    data = np.array([1, 2, 3, 4, 5, 6, 7, 6, 5, 4, 3, 2, 1])
+    result = calculate_distance_to_last_trend_change(data, window_size=3)
+    assert isinstance(result, int), "Result should be an integer for numpy array input"
+    assert result >= 0, "Distance should be a non-negative integer"
\ No newline at end of file
diff --git a/tests/core/features/test_feature_binarize_mean.py b/tests/core/features/test_feature_binarize_mean.py
new file mode 100644
index 0000000..0c57f1b
--- /dev/null
+++ b/tests/core/features/test_feature_binarize_mean.py
@@ -0,0 +1,23 @@
+import pytest
+import pandas as pd
+import numpy as np
+from interpreTS.core.features.feature_binarize_mean import calculate_binarize_mean
+
+def test_calculate_binarize_mean():
+    data = pd.Series([1, 2, 3, 4, 5])
+    expected_result = 0.4  # 3 out of 5 elements are above the mean (3)
+    assert calculate_binarize_mean(data) == expected_result, "The binarize mean should be 0.6 for this series"
+
+def test_calculate_binarize_mean_empty_series():
+    data = pd.Series([])
+    assert pd.isna(calculate_binarize_mean(data)), "The binarize mean of an empty series should be NaN"
+
+def test_calculate_binarize_mean_with_numpy_array():
+    data = np.array([1, 2, 3, 4, 5])
+    expected_result = 0.4
+    assert calculate_binarize_mean(pd.Series(data)) == expected_result, "The binarize mean should be 0.6 for this numpy array"
+
+def test_calculate_binarize_mean_negative_values():
+    data = pd.Series([-1, -2, -3, -4, -5])
+    expected_result = 0.4  # 2 out of 5 elements are above the mean (-3)
+    assert calculate_binarize_mean(data) == expected_result, "The binarize mean should correctly handle negative values"
diff --git a/tests/core/features/test_feature_crossing_points.py b/tests/core/features/test_feature_crossing_points.py
new file mode 100644
index 0000000..5ade7d5
--- /dev/null
+++ b/tests/core/features/test_feature_crossing_points.py
@@ -0,0 +1,58 @@
+import pytest
+import pandas as pd
+import numpy as np
+from interpreTS.core.features.feature_crossing_points import calculate_crossing_points
+
+def test_crossing_points_basic():
+    """
+    Test basic functionality with a typical time series.
+    """
+    data = pd.Series([1, 2, 3, 2, 1, 3, 1, 0])
+    result = calculate_crossing_points(data)
+    assert result['crossing_count'] == 4, "Crossing count should be 4"
+    assert result['crossing_points'] == [0, 3, 4, 5], "Crossing points should be [1, 4, 5, 6]"
+
+def test_crossing_points_one_crossing():
+    """
+    Test with a series that crosses its mean only once.
+    """
+    data = pd.Series([1, 2, 3, 4, 5])
+    result = calculate_crossing_points(data)
+    assert result['crossing_count'] == 1, "Crossing count should be 1"
+    assert result['crossing_points'] == [2], "Crossing points should be [2]"
+
+def test_crossing_points_negative_values():
+    """
+    Test with a series that includes negative values and crosses its mean multiple times.
+    """
+    data = pd.Series([-3, -1, 1, 3, -2, 2, -1, 1])
+    result = calculate_crossing_points(data)
+    assert result['crossing_count'] == 5, "Crossing count should be 5"
+    assert result['crossing_points'] == [1, 3, 4, 5, 6], "Crossing points should be [1, 3, 4, 5, 6]"
+
+def test_crossing_points_empty_series():
+    """
+    Test with an empty series to ensure it handles edge cases gracefully.
+    """
+    data = pd.Series([])
+    result = calculate_crossing_points(data)
+    assert result['crossing_count'] == 0, "Crossing count should be 0 for an empty series"
+    assert result['crossing_points'] == [], "Crossing points should be an empty list for an empty series"
+
+def test_crossing_points_all_above_mean():
+    """
+    Test a case where all values are well above the mean.
+    """
+    data = pd.Series([12, 12, 12, 12, 12])
+    result = calculate_crossing_points(data)
+    assert result['crossing_count'] == 0, "Crossing count should be 0 when all values are above mean"
+    assert result['crossing_points'] == [], "Crossing points should be an empty list when all values are above mean"
+
+def test_crossing_points_all_below_mean():
+    """
+    Test a case where all values are well below the mean.
+    """
+    data = pd.Series([-12, -12, -12, -12, -12])
+    result = calculate_crossing_points(data)
+    assert result['crossing_count'] == 0, "Crossing count should be 0 when all values are below mean"
+    assert result['crossing_points'] == [], "Crossing points should be an empty list when all values are below mean"
diff --git a/tests/core/features/test_feature_entropy.py b/tests/core/features/test_feature_entropy.py
new file mode 100644
index 0000000..06d116b
--- /dev/null
+++ b/tests/core/features/test_feature_entropy.py
@@ -0,0 +1,29 @@
+import pytest
+import pandas as pd
+import numpy as np
+from interpreTS.core.features.feature_entropy import calculate_entropy
+
+def test_calculate_entropy_with_valid_series():
+    data = pd.Series([1, 2, 3, 4, 3, 6, 7, 6, 9, 10])
+    entropy = calculate_entropy(data)
+    assert 0 < entropy < 1, "Entropy should be between 0 and 1"
+
+def test_calculate_entropy_with_constant_series():
+    data = pd.Series([5, 5, 5, 5, 5])
+    entropy = calculate_entropy(data)
+    assert entropy == 0.0, "Entropy of a constant series should be 0"
+    
+def test_calculate_entropy_with_constant_changes():
+    data = pd.Series([5, 6, 7, 8, 9])
+    entropy = calculate_entropy(data)
+    assert entropy == 1.0, "Entropy of a lineary changing series should be 1"
+    
+def test_calculate_entropy_with_empty_series():
+    data = pd.Series([])
+    with pytest.raises(ValueError, match="The input data is empty."):
+        calculate_entropy(data)
+
+def test_calculate_entropy_with_short_series():
+    data = pd.Series([1])
+    with pytest.raises(ValueError, match="The input data is too short to calculate entropy."):
+        calculate_entropy(data)
\ No newline at end of file
diff --git a/tests/core/features/test_feature_flat_spots.py b/tests/core/features/test_feature_flat_spots.py
new file mode 100644
index 0000000..b9ab7d5
--- /dev/null
+++ b/tests/core/features/test_feature_flat_spots.py
@@ -0,0 +1,29 @@
+import pytest
+import pandas as pd
+import numpy as np
+from interpreTS.core.features.feature_flat_spots import calculate_flat_spots
+
+def test_calculate_flat_spots_with_flat_spots():
+    data = pd.Series([1, 1, 1, 2, 3, 3, 4, 4, 4, 4, 4, 5, 1, 1])
+    result = calculate_flat_spots(data)
+    assert result == 4, "Expected 4 flat spots"
+
+def test_calculate_flat_spots_without_flat_spots():
+    data = pd.Series([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+    result = calculate_flat_spots(data)
+    assert result == 1, "Expected 1 flat spot"
+
+def test_calculate_flat_spots_empty_series():
+    data = pd.Series([])
+    result = calculate_flat_spots(data)
+    assert result == 0, "Expected 0 flat spots for empty series"
+
+def test_calculate_flat_spots_constant_values():
+    data = pd.Series([5, 5, 5, 5, 5])
+    result = calculate_flat_spots(data)
+    assert result == 5, "Expected 10 flat spots for constant values"
+
+def test_calculate_flat_spots_different_window_size():
+    data = pd.Series([1, 1, 1, 2, 3, 3, 4, 4, 4, 4, 4, 5, 1, 1])
+    result = calculate_flat_spots(data, window_size=3)
+    assert result == 3, "Expected 3 flat spots with window size 3"
\ No newline at end of file
diff --git a/tests/core/features/test_feature_stability.py b/tests/core/features/test_feature_stability.py
new file mode 100644
index 0000000..c1ea151
--- /dev/null
+++ b/tests/core/features/test_feature_stability.py
@@ -0,0 +1,52 @@
+import pytest
+import pandas as pd
+import numpy as np
+from interpreTS.core.features.feature_stability import calculate_stability
+
+def test_stability_high_stability():
+    """
+    Test calculate_stability on a highly stable time series.
+    """
+    data = pd.Series([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
+    stability = calculate_stability(data, 2)
+    assert stability == 1.0, "Stability should be 1 for a perfectly stable series"
+
+def test_stability_low_stability():
+    """
+    Test calculate_stability on a low stability time series.
+    """
+    data = pd.Series([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
+    stability = calculate_stability(data)
+    assert stability < 1.0, "Stability should be less than 1 for a non-stable series"
+
+def test_two_stabilities():
+    """
+    Test calculate_stability on two different time series.
+    """
+    data1 = pd.Series([1, 2, 1, 1, 1, 1, 1, 1])
+    data2 = pd.Series([1, 2, 1, 2, 1, 2, 1, 1])
+    assert calculate_stability(data1) > calculate_stability(data2), "Stability should be higher for the first series"
+
+def test_stability_empty_series():
+    """
+    Test calculate_stability on an empty time series.
+    """
+    data = pd.Series([], dtype="float64")
+    stability = calculate_stability(data)
+    assert np.isnan(stability), "Stability should be NaN for an empty series"
+
+def test_stability_with_nan_values():
+    """
+    Test calculate_stability on a time series containing NaN values.
+    """
+    data = pd.Series([1, 2, np.nan, 4, 5])
+    with pytest.raises(ValueError, match="Data contains NaN values"):
+        calculate_stability(data)
+
+def test_stability_numpy_array():
+    """
+    Test calculate_stability on a numpy array (alternative data format).
+    """
+    data = np.array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
+    stability = calculate_stability(pd.Series(data))
+    assert stability == 1.0, "Stability should be 1 for a perfectly stable series"
\ No newline at end of file
diff --git a/tests/core/features/test_feature_variance.py b/tests/core/features/test_feature_variance.py
index 0014426..d8d673a 100644
--- a/tests/core/features/test_feature_variance.py
+++ b/tests/core/features/test_feature_variance.py
@@ -8,7 +8,7 @@ def test_calculate_variance():
     Test that calculate_variance correctly calculates the variance of a time series.
     """
     data = pd.Series([1, 2, 3, 4, 5])
-    assert calculate_variance(data) == 2.0, "The variance value should be 2.0"
+    assert calculate_variance(data) == 2.5, "The variance value should be 2.5"
 
 def test_calculate_variance_empty_series():
     """
@@ -22,11 +22,32 @@ def test_calculate_variance_numpy_array():
     Test that calculate_variance correctly calculates the variance of a numpy array.
     """
     data = np.array([1, 2, 3, 4, 5])
-    assert calculate_variance(data) == 2.0, "The variance should be 2.0 for numpy array"
+    assert calculate_variance(data) == 2.5, "The variance should be 2.5 for numpy array"
 
 def test_calculate_variance_negative_values():
     """
     Test that calculate_variance correctly calculates the variance for a series with negative values.
     """
     data = pd.Series([-1, -2, -3, -4, -5])
-    assert calculate_variance(data) == 2.0, "The mean value should be 2.0"
+    assert calculate_variance(data) == 2.5, "The mean value should be 2.0"
+
+def test_calculate_variance_with_ddof_0():
+    """
+    Test that calculate_variance correctly calculates the population variance (ddof=0).
+    """
+    data = pd.Series([1, 2, 3, 4, 5])
+    assert calculate_variance(data, ddof=0) == 2.0, "The population variance should be 2.0"
+
+def test_calculate_variance_with_single_value():
+    """
+    Test that calculate_variance returns 0 for a series with a single value.
+    """
+    data = pd.Series([42])
+    assert calculate_variance(data) == 0.0, "The variance of a single value series should be 0.0"
+
+def test_calculate_variance_with_identical_values():
+    """
+    Test that calculate_variance returns 0 for a series with identical values.
+    """
+    data = pd.Series([5, 5, 5, 5, 5])
+    assert calculate_variance(data) == 0.0, "The variance of identical values should be 0.0"
\ No newline at end of file
diff --git a/tests/core/features/test_mean_change.py b/tests/core/features/test_mean_change.py
new file mode 100644
index 0000000..a262ad1
--- /dev/null
+++ b/tests/core/features/test_mean_change.py
@@ -0,0 +1,71 @@
+import pytest
+import pandas as pd
+import numpy as np
+from interpreTS.core.features.mean_change import calculate_mean_change  
+
+def test_calculate_mean_change_constant_data():
+    """
+    Test that calculate_mean_change returns zeros for constant data.
+    """
+    data = pd.Series([5] * 10)  # Constant series
+    result = calculate_mean_change(data, window_size=3)
+    assert result.dropna().eq(0).all(), "Change in mean should be zero for constant data"
+
+def test_calculate_mean_change_increasing_data():
+    """
+    Test that calculate_mean_change detects increasing means.
+    """
+    data = pd.Series([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])  # Increasing trend
+    result = calculate_mean_change(data, window_size=3)
+    assert result.dropna().iloc[-1] > 0, "Change in mean should increase for growing data"
+
+def test_calculate_mean_change_decreasing_data():
+    """
+    Test that calculate_mean_change detects decreasing means.
+    """
+    data = pd.Series([10, 9, 8, 7, 6, 5, 4, 3, 2, 1])  # Decreasing trend
+    result = calculate_mean_change(data, window_size=3)
+    assert result.dropna().iloc[-1] < 0, "Change in mean should decrease for shrinking data"
+
+def test_calculate_mean_change_random_data():
+    """
+    Test that calculate_mean_change handles random data without errors.
+    """
+    np.random.seed(0)
+    data = pd.Series(np.random.randn(100))  # Random normal data
+    result = calculate_mean_change(data, window_size=5)
+    assert not result.isnull().all(), "Change in mean should not be all NaN for valid random data"
+
+def test_calculate_mean_change_empty_series():
+    """
+    Test that calculate_mean_change raises an error for an empty series.
+    """
+    data = pd.Series([])
+    with pytest.raises(ValueError, match="too short"):
+        calculate_mean_change(data, window_size=3)
+
+def test_calculate_mean_change_insufficient_data():
+    """
+    Test that calculate_mean_change raises an error for insufficient data.
+    """
+    data = pd.Series([1, 2])
+    with pytest.raises(ValueError, match="too short"):
+        calculate_mean_change(data, window_size=3)
+
+def test_calculate_mean_change_window_size():
+    """
+    Test that calculate_mean_change respects the specified window size.
+    """
+    data = pd.Series([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+    window_size = 4
+    result = calculate_mean_change(data, window_size=window_size)
+    assert len(result.dropna()) == len(data) - window_size, "Result should have the correct length based on window size"
+
+def test_calculate_mean_change_numpy_array():
+    """
+    Test that calculate_mean_change works with a numpy array.
+    """
+    data = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+    result = calculate_mean_change(data, window_size=3)
+    assert isinstance(result, pd.Series), "Result should be a pandas Series for numpy input"
+    assert len(result) == len(data), "Result should have the same length as the input data"
\ No newline at end of file
diff --git a/tests/core/features/test_missing_points.py b/tests/core/features/test_missing_points.py
new file mode 100644
index 0000000..2f74e8c
--- /dev/null
+++ b/tests/core/features/test_missing_points.py
@@ -0,0 +1,73 @@
+import pytest
+import pandas as pd
+import numpy as np
+from interpreTS.core.features.feature_missing_points import missing_points
+
+def test_missing_points_basic():
+    """
+    Test basic functionality with a typical time series.
+    """
+    data = pd.Series([1, 2, np.nan, 4, None, 5])
+    percentage_result = missing_points(data)
+    count_result = missing_points(data, percentage=False)
+    assert percentage_result == 0.3333333333333333, "Percentage of missing points should be ~33.33%"
+    assert count_result == 2, "Missing points count should be 2"
+
+def test_missing_points_no_missing():
+    """
+    Test with a series that has no missing values.
+    """
+    data = pd.Series([1, 2, 3, 4, 5])
+    percentage_result = missing_points(data)
+    count_result = missing_points(data, percentage=False)
+    assert percentage_result == 0.0, "Percentage of missing points should be 0%"
+    assert count_result == 0, "Missing points count should be 0"
+
+def test_missing_points_all_missing():
+    """
+    Test with a series where all values are missing.
+    """
+    data = pd.Series([np.nan, None, np.nan])
+    percentage_result = missing_points(data)
+    count_result = missing_points(data, percentage=False)
+    assert percentage_result == 1.0, "Percentage of missing points should be 100%"
+    assert count_result == 3, "Missing points count should be 3"
+
+def test_missing_points_empty_series():
+    """
+    Test with an empty series to ensure it handles edge cases gracefully.
+    """
+    data = pd.Series([])
+    percentage_result = missing_points(data)
+    count_result = missing_points(data, percentage=False)
+    assert np.isnan(percentage_result), "Percentage of missing points should be NaN for an empty series"
+    assert np.isnan(count_result), "Missing points count should be NaN for an empty series"
+
+def test_missing_points_mixed_types():
+    """
+    Test with a series containing mixed types including NaN values.
+    """
+    data = pd.Series([1, "text", None, 3.5, np.nan, True])
+    percentage_result = missing_points(data)
+    count_result = missing_points(data, percentage=False)
+    assert percentage_result == 0.3333333333333333, "Percentage of missing points should be ~33.33%"
+    assert count_result == 2, "Missing points count should be 2"
+
+def test_missing_points_only_one_value():
+    """
+    Test with a series containing only one value, either valid or NaN.
+    """
+    data_valid = pd.Series([10])
+
+    percentage_result_valid = missing_points(data_valid)
+    count_result_valid = missing_points(data_valid, percentage=False)
+    assert percentage_result_valid == 0.0, "Percentage of missing points should be 0% for a single valid value"
+    assert count_result_valid == 0, "Missing points count should be 0 for a single valid value"
+
+def test_missing_points_only_one_missing_value():
+    data_missing = pd.Series([np.nan])
+        
+    percentage_result_missing = missing_points(data_missing)
+    count_result_missing = missing_points(data_missing, percentage=False)
+    assert percentage_result_missing == 1.0, "Percentage of missing points should be 100% for a single NaN value"
+    assert count_result_missing == 1, "Missing points count should be 1 for a single NaN value"
diff --git a/tests/core/features/test_variability_in_sub_periods.py b/tests/core/features/test_variability_in_sub_periods.py
new file mode 100644
index 0000000..b886a4b
--- /dev/null
+++ b/tests/core/features/test_variability_in_sub_periods.py
@@ -0,0 +1,86 @@
+import pytest
+import pandas as pd
+import numpy as np
+from interpreTS.core.features.variability_in_sub_periods import calculate_variability_in_sub_periods
+
+def test_calculate_variability_in_sub_periods_strong_variability():
+    """
+    Test that calculate_variability_in_sub_periods identifies strong variability
+    in a time series with large fluctuations.
+    """
+    data = pd.Series([1, 10, 1, 10, 1, 10, 1, 10])
+    result = calculate_variability_in_sub_periods(data, window_size=4)
+    assert all(val > 10 for val in result), "The variability should be high for data with strong fluctuations"
+
+def test_calculate_variability_in_sub_periods_weak_variability():
+    """
+    Test that calculate_variability_in_sub_periods identifies weak variability
+    in a time series with small fluctuations.
+    """
+    data = pd.Series([1, 1.1, 1.2, 1.1, 1, 1.05, 1.1, 1.15])
+    result = calculate_variability_in_sub_periods(data, window_size=4)
+    assert all(val < 0.1 for val in result), "The variability should be low for data with weak fluctuations"
+
+def test_calculate_variability_in_sub_periods_no_variability():
+    """
+    Test that calculate_variability_in_sub_periods identifies no variability
+    in a time series with constant values.
+    """
+    data = pd.Series([5, 5, 5, 5, 5, 5, 5, 5])
+    result = calculate_variability_in_sub_periods(data, window_size=4)
+    assert all(val == 0 for val in result), "The variability should be zero for constant data"
+
+def test_calculate_variability_in_sub_periods_empty_series():
+    """
+    Test that calculate_variability_in_sub_periods returns an empty list for an empty series.
+    """
+    data = pd.Series([])
+    with pytest.raises(ValueError, match="Window size must be smaller than or equal to the length of the data"):
+        calculate_variability_in_sub_periods(data, window_size=4)
+
+def test_calculate_variability_in_sub_periods_insufficient_data():
+    """
+    Test that calculate_variability_in_sub_periods returns an empty result when data
+    is insufficient for the specified window size.
+    """
+    data = pd.Series([5, 6])
+    with pytest.raises(ValueError, match="Window size must be smaller than or equal to the length of the data"):
+        calculate_variability_in_sub_periods(data, window_size=4)
+
+def test_calculate_variability_in_sub_periods_with_numpy_array():
+    """
+    Test that calculate_variability_in_sub_periods works correctly with a numpy array.
+    """
+    data = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+    result = calculate_variability_in_sub_periods(data, window_size=5)
+    assert len(result) == 2, "The result should have two values for a numpy array with a window size of 5"
+    assert all(isinstance(val, float) for val in result), "Each result should be a float (variance value)"
+
+def test_calculate_variability_in_sub_periods_with_overlap():
+    """
+    Test that calculate_variability_in_sub_periods handles overlapping sub-periods correctly.
+    """
+    data = pd.Series([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+    result = calculate_variability_in_sub_periods(data, window_size=5, step_size=2)
+    expected_length = (len(data) - 5) // 2 + 1  # Expected number of overlapping windows
+    assert len(result) == expected_length, "Overlap calculation failed"
+    assert all(isinstance(v, float) for v in result), "Each result should be a float (variance value)"
+
+def test_calculate_variability_in_sub_periods_high_variance():
+    """
+    Test that calculate_variability_in_sub_periods identifies high variance in sub-periods
+    with data that fluctuates greatly within each window.
+    """
+    data = pd.Series([1, 100, 1, 100, 1, 100, 1, 100, 1, 100])
+    result = calculate_variability_in_sub_periods(data, window_size=3)
+    assert all(val > 1000 for val in result), "The variability should be high for high-fluctuation data"
+
+def test_calculate_variability_in_sub_periods_with_small_window_size():
+    """
+    Test that calculate_variability_in_sub_periods correctly calculates variability with a small window size.
+    """
+    data = pd.Series([1, 2, 3, 4, 5])
+    result = calculate_variability_in_sub_periods(data, window_size=2, step_size=1)  # Set step_size to 1 for overlapping windows
+    expected_length = len(data) - 1
+    assert len(result) == expected_length, f"The number of results should be {expected_length} with a window size of 2 and step size of 1"
+    assert np.isclose(result.iloc[0], 0.25, atol=1e-2), "The variance of the first window should be approximately 0.25"
diff --git a/tests/core/features/test_variance_change.py b/tests/core/features/test_variance_change.py
new file mode 100644
index 0000000..dd7e3eb
--- /dev/null
+++ b/tests/core/features/test_variance_change.py
@@ -0,0 +1,75 @@
+import pytest
+import pandas as pd
+import numpy as np
+from interpreTS.core.features.variance_change import calculate_change_in_variance 
+
+def test_calculate_change_in_variance_constant_data():
+    """
+    Test that calculate_change_in_variance returns zeros for constant data.
+    """
+    data = pd.Series([5] * 10)  # Constant series
+    result = calculate_change_in_variance(data, window_size=3)
+    assert result.dropna().eq(0).all(), "Change in variance should be zero for constant data"
+
+def test_calculate_change_in_variance_increasing_variance():
+    """
+    Test that calculate_change_in_variance detects increasing variance.
+    """
+    data = pd.Series([1, 2, 3, 4, 5, 10, 20, 30, 40, 50])  # Increasing variability
+    result = calculate_change_in_variance(data, window_size=3)
+    assert result.dropna().iloc[-1] >= 0, "Change in variance should increase for growing variability"
+
+def test_calculate_change_in_variance_random_data():
+    """
+    Test that calculate_change_in_variance handles random data without errors.
+    """
+    np.random.seed(0)
+    data = pd.Series(np.random.randn(100))  # Random normal data
+    result = calculate_change_in_variance(data, window_size=5)
+    assert not result.isnull().all(), "Change in variance should not be all NaN for valid random data"
+
+def test_calculate_change_in_variance_empty_series():
+    """
+    Test that calculate_change_in_variance returns NaN for an empty series.
+    """
+    data = pd.Series([])
+    try:
+        result = calculate_change_in_variance(data, window_size=3)
+    except ValueError as e:
+        assert "too short" in str(e), "Function should raise a ValueError for empty series"
+
+def test_calculate_change_in_variance_insufficient_data():
+    """
+    Test that calculate_change_in_variance raises an error for insufficient data.
+    """
+    data = pd.Series([1, 2])
+    try:
+        result = calculate_change_in_variance(data, window_size=3)
+    except ValueError as e:
+        assert "too short" in str(e), "Function should raise a ValueError for insufficient data"
+
+def test_calculate_change_in_variance_window_size():
+    """
+    Test that calculate_change_in_variance respects the specified window size.
+    """
+    data = pd.Series([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+    window_size = 4
+    result = calculate_change_in_variance(data, window_size=window_size)
+    assert len(result.dropna()) == len(data) - window_size, "Result should have the correct length based on window size"
+
+def test_calculate_change_in_variance_numpy_array():
+    """
+    Test that calculate_change_in_variance works with a numpy array.
+    """
+    data = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+    result = calculate_change_in_variance(data, window_size=3)
+    assert isinstance(result, pd.Series), "Result should be a pandas Series for numpy input"
+    assert len(result) == len(data), "Result should have the same length as the input data"
+
+def test_calculate_change_in_variance_decreasing_variance():
+    """
+    Test that calculate_change_in_variance detects decreasing variance.
+    """
+    data = pd.Series([50, 40, 30, 20, 10, 5, 4, 3, 2, 1])  # Decreasing variability
+    result = calculate_change_in_variance(data, window_size=3)
+    assert result.dropna().iloc[-1] <= 0, "Change in variance should decrease for shrinking variability"
diff --git a/tests/core/test_feature_extractor.py b/tests/core/test_feature_extractor.py
index 3772900..1df5204 100644
--- a/tests/core/test_feature_extractor.py
+++ b/tests/core/test_feature_extractor.py
@@ -84,7 +84,7 @@ def test_extract_variance_feature():
     data = pd.DataFrame({'value': [1, 2, 3, 4, 5]})
     extractor = FeatureExtractor(features=[Features.VARIANCE])
     features = extractor.extract_features(data)
-    assert np.isclose(features[Features.VARIANCE].iloc[0], 2.0, atol=1e-4), "The 'variance' feature should be 2.0"
+    assert np.isclose(features[Features.VARIANCE].iloc[0], 2.5, atol=1e-4), "The 'variance' feature should be 2.5"
 
 def test_extract_peak_and_trough_features():
     """
@@ -110,3 +110,46 @@ def test_extract_with_sort_column():
     features = extractor.extract_features(data)
     assert Features.MEAN in features.columns, "The 'mean' feature should be in the features DataFrame"
     assert features[Features.MEAN].iloc[0] == 20, "The mean should be calculated after sorting by 'time'"
+
+def test_extract_absolute_energy_feature():
+    """
+    Test that FeatureExtractor correctly extracts the 'absolute_energy' feature.
+    """
+    data = pd.Series([1, 2, 3, -4, 5])
+    extractor = FeatureExtractor(features=[Features.ABSOLUTE_ENERGY])
+    features = extractor.extract_features(data)
+    assert np.isclose(features[Features.ABSOLUTE_ENERGY].iloc[0], 55.0, atol=1e-4), "The 'absolute_energy' feature should be 55.0"
+    
+def test_extract_missing_points_feature():
+    """
+    Test that FeatureExtractor correctly extracts the 'missing_points' feature.
+    """
+    data = pd.DataFrame({'value': [1, 3, None, 1, np.nan]})
+    extractor = FeatureExtractor(features=[Features.MISSING_POINTS])
+    features = extractor.extract_features(data)
+    extractor2 = FeatureExtractor(features=[Features.MISSING_POINTS, ], feature_params={Features.MISSING_POINTS: {'percentage': False}})
+    features2 = extractor2.extract_features(data)
+    
+    assert features[Features.MISSING_POINTS].iloc[0] == 0.4, "The percentage of missing points should be 40%"
+    assert features2[Features.MISSING_POINTS].iloc[0] == 2, "The amount of missing points should be 2"
+
+def test_extract_entropy_feature():
+    """
+    Test that FeatureExtractor correctly extracts the 'entropy' feature.
+    """
+    data = pd.Series([1, -2, 9, 10, 15])
+    extractor = FeatureExtractor(features=[Features.ENTROPY])
+    features = extractor.extract_features(data)
+    assert 0 < features[Features.ENTROPY].iloc[0] < 1, "The 'entropy' feature should be between 0 and 1"
+
+def test_extract_stability_features():
+    """
+    Test that FeatureExtractor correctly extracts the 'peak' and 'trough' features.
+    """
+    data = pd.DataFrame({'value': [1, 3, 1, 3, 1]})
+    data2 = pd.DataFrame({'value': [1, 1, 1, 1, 1]})
+    extractor = FeatureExtractor(features=[Features.STABILITY])
+    features = extractor.extract_features(data)
+    features2 = extractor.extract_features(data2)
+    assert features[Features.STABILITY].iloc[0] < 1, "The stability feature should be less than 1 if the data is not constant"
+    assert features2[Features.STABILITY].iloc[0] == 1, "The stability feature should be 1 for constant data"
diff --git a/tests/core/test_streaming_feature_extractor.py b/tests/core/test_streaming_feature_extractor.py
new file mode 100644
index 0000000..85d2f05
--- /dev/null
+++ b/tests/core/test_streaming_feature_extractor.py
@@ -0,0 +1,94 @@
+import pytest
+import pandas as pd
+from interpreTS.core.streaming_feature_extractor import StreamingFeatureExtractor
+
+def test_streaming_extractor_with_mean():
+    """
+    Test that StreamingFeatureExtractor calculates mean correctly.
+    """
+    extractor = StreamingFeatureExtractor(features=['mean'], window_size=3)
+    data_points = [1, 2, 3]
+    for point in data_points:
+        result = extractor.add_data(point)
+    assert result['mean'] == 2.0, "Mean should be 2.0 for [1, 2, 3]"
+
+def test_streaming_extractor_with_variance():
+    """
+    Test that StreamingFeatureExtractor calculates variance correctly.
+    """
+    extractor = StreamingFeatureExtractor(features=['variance'], window_size=3)
+    data_points = [1, 2, 3]
+    for point in data_points:
+        result = extractor.add_data(point)
+    assert result['variance'] == 1.0, "Variance should be 1.0 for [1, 2, 3]"
+
+def test_streaming_extractor_with_crossing_points():
+    """
+    Test that StreamingFeatureExtractor calculates crossing points correctly.
+    """
+    extractor = StreamingFeatureExtractor(features=['crossing_points'], window_size=5)
+    data_points = [1, 2, 3, 2, 1]  # Wartości, które powinny przechodzić przez średnią w punktach 1 i 3.
+
+    result = None
+    for point in data_points:
+        result = extractor.add_data(point)
+
+    assert result is not None, "Result should not be None after processing data points."
+    assert result['crossing_points']['crossing_count'] == 2, "Crossing count should be 2 for [1, 2, 3, 2, 1]"
+    assert result['crossing_points']['crossing_points'] == [0, 3], "Crossing points should be [1, 4] for [1, 2, 3, 2, 1]"
+    
+def test_streaming_extractor_with_spikeness():
+    """
+    Test that StreamingFeatureExtractor calculates spikeness correctly.
+    """
+    extractor = StreamingFeatureExtractor(features=['spikeness'], window_size=5)
+    data_points = [1, 10, 1, 10, 1]
+    for point in data_points:
+        result = extractor.add_data(point)
+    assert result['spikeness'] > 0, "Spikeness should be greater than 0 for [1, 10, 1, 10, 1]"
+
+def test_streaming_extractor_with_flat_spots():
+    """
+    Test that StreamingFeatureExtractor calculates the maximum run-length of flat spots correctly.
+    """
+    extractor = StreamingFeatureExtractor(features=['flat_spots'], window_size=5)
+    data_points = [2, 2, 3, 3, 3]
+    for point in data_points:
+        result = extractor.add_data(point)
+    assert result['flat_spots'] == 3, "Flat spots should be 3 for [2, 2, 3, 3, 3]"
+
+
+def test_streaming_extractor_no_output_before_window():
+    """
+    Test that StreamingFeatureExtractor returns None before reaching window size.
+    """
+    extractor = StreamingFeatureExtractor(features=['mean'], window_size=4)
+    data_points = [1, 2, 3]
+    for point in data_points:
+        result = extractor.add_data(point)
+        assert result is None, "No output should be generated before reaching window size"
+
+def test_streaming_extractor_with_multiple_features():
+    """
+    Test that StreamingFeatureExtractor calculates multiple features correctly.
+    """
+    extractor = StreamingFeatureExtractor(features=['mean', 'variance', 'peak'], window_size=3)
+    data_points = [2, 4, 6]
+    for point in data_points:
+        result = extractor.add_data(point)
+    assert result['mean'] == 4.0, "Mean should be 4.0 for [2, 4, 6]"
+    assert result['variance'] == 4.0, "Variance should be 4.0 for [2, 4, 6]"
+    assert result['peak'] == 6, "Peak should be 6 for [2, 4, 6]"
+
+def test_streaming_extractor_with_rolling_window():
+    """
+    Test that StreamingFeatureExtractor calculates features correctly with rolling window.
+    """
+    extractor = StreamingFeatureExtractor(features=['mean'], window_size=3)
+    data_points = [1, 2, 3, 4, 5]
+    results = []
+    for point in data_points:
+        result = extractor.add_data(point)
+        if result:
+            results.append(result['mean'])
+    assert results == [2.0, 3.0, 4.0], "Mean should be [2.0, 3.0, 4.0] with rolling window for data [1, 2, 3, 4, 5]"
diff --git a/tests/utils/test_validate_time_series_data.py b/tests/utils/test_validate_time_series_data.py
index 77c16fc..3aa81b3 100644
--- a/tests/utils/test_validate_time_series_data.py
+++ b/tests/utils/test_validate_time_series_data.py
@@ -1,6 +1,7 @@
 import pytest
 import pandas as pd
 import numpy as np
+
 from interpreTS.utils.data_validation import validate_time_series_data
 
 def test_validate_pandas_series():