{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Matplotlib Linear Regression Animation in Jupyter Notebook\n", "\n", "This is a notebook for the medium article [Matplotlib Linear Regression Animation in Jupyter Notebook](https://bindichen.medium.com/matplotlib-linear-regression-animation-in-jupyter-notebook-2435b711bea2)\n", "\n", "Please check out article for instructions\n", "\n", "**License**: [BSD 2-Clause](https://opensource.org/licenses/BSD-2-Clause)" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "%%capture\n", "!conda install -c conda-forge -y ffmpeg pillow\n", "!pip install vega_datasets ipympl scikit-learn ffmpeg-python" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "%matplotlib widget\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", "from sklearn.linear_model import LinearRegression\n", "from vega_datasets import data\n", "\n", "%matplotlib inline\n", "%config InlineBackend.figure_format = 'svg'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Data Preprocessing" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
NameMiles_per_GallonCylindersDisplacementHorsepowerWeight_in_lbsAccelerationYearOrigin
0chevrolet chevelle malibu18.08307.0130.0350412.01970-01-01USA
1buick skylark 32015.08350.0165.0369311.51970-01-01USA
2plymouth satellite18.08318.0150.0343611.01970-01-01USA
3amc rebel sst16.08304.0150.0343312.01970-01-01USA
4ford torino17.08302.0140.0344910.51970-01-01USA
\n", "
" ], "text/plain": [ " Name Miles_per_Gallon Cylinders Displacement \\\n", "0 chevrolet chevelle malibu 18.0 8 307.0 \n", "1 buick skylark 320 15.0 8 350.0 \n", "2 plymouth satellite 18.0 8 318.0 \n", "3 amc rebel sst 16.0 8 304.0 \n", "4 ford torino 17.0 8 302.0 \n", "\n", " Horsepower Weight_in_lbs Acceleration Year Origin \n", "0 130.0 3504 12.0 1970-01-01 USA \n", "1 165.0 3693 11.5 1970-01-01 USA \n", "2 150.0 3436 11.0 1970-01-01 USA \n", "3 150.0 3433 12.0 1970-01-01 USA \n", "4 140.0 3449 10.5 1970-01-01 USA " ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df = data.cars()\n", "df.head()" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "# Drop rows with NaN\n", "df.dropna(subset=['Horsepower', 'Miles_per_Gallon'], inplace=True)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "# Transform data\n", "x = df['Horsepower'].to_numpy().reshape(-1, 1)\n", "y = df['Miles_per_Gallon'].to_numpy().reshape(-1, 1)" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "image/svg+xml": "\n\n\n \n \n \n \n 2022-10-18T14:12:19.457955\n image/svg+xml\n \n \n Matplotlib v3.6.0, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plt.scatter(x, y, c='g', label='Horsepower vs. Miles_per_Gallon')\n", "plt.legend()\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 1. Animations with Interactive Plot" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Warning: Cannot change to a different GUI toolkit: notebook. Using widget instead.\n", "Warning: Cannot change to a different GUI toolkit: notebook. Using widget instead.\n" ] } ], "source": [ "# Enable interactive plot\n", "%matplotlib notebook\n" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "from matplotlib.animation import FuncAnimation\n", "from sklearn.linear_model import LinearRegression" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "0ed500966b80441180764cbcd5d94cbd", "version_major": 2, "version_minor": 0 }, "image/png": "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unfvri+//NLbn5iYqDZt2uj111/3fsp68ODBPjOTxcXF+tOf/qSGDRvKbrerTZs2Wr9+vbf/3nvv1dixY723J0yYIJvNpszMTEnSmTNnFBISog0bNkj6OVjMmzdPN9xwg4KDgxUXF6e//e1v3u3PzY5u2LBB7du3l91u16efflri8YqPj9dTTz3l0/bjjz+qWrVq2rx5syTptddeU0xMjGrUqKGIiAjdd999ZT4e5wwfPlypqanKycnxtr3zzjsaPnx4iXB0/iXg0nz11Ve64447VLt2bUVEROjBBx/UTz/95O3/29/+platWik4OFj16tVTz549deLEicvWeG6medasWd5j/eijj6qoqMi7ztV63M/34osvauvWrfrkk080btw43XLLLWrWrJn69OmjhQsXas6cOd51169fr9/85jeqU6eO6tWrp7vuukvffvvtZcd2vrKcI3PmzNHIkSMVEhKiRo0a6Y033rii+8D/IQACqNTOFp/V+PXjZVTyVy3PtU1YP6HcLwdfSnp6uu6//3498MADysjIUGJiombMmOFzeVGSnnvuOcXGxio9PV0zZszQK6+8otWrV+vDDz/U3r179d5776lx48aSfn7Bv/POO5WXl6e1a9cqPT1d7dq1U48ePXT48GHvPr/55ht9+OGH+sc//qH169dr9+7dGj16tLf/5Zdf1gsvvKDnn39ee/bsUZ8+fXT33XcrOztbktS1a1elpKR4109NTVX9+vWVmpoqSUpLS9OpU6fUqVMnSdLTTz+tRYsWaeHChcrMzNTjjz+u3/72t971z5k8ebKSkpKUlZWl1q1bl3jMhg8frg8++EDn/1rp8uXLFRERoS5dumjnzp0aN26c/vSnP2nv3r1av369OnfufMXHJiIiQn369NGSJUskSSdPntTy5cs1cuTIK9pPbm6uunTpojZt2mjnzp1av369Dh48qPvvv9/bP3ToUI0cOVJZWVlKSUnRoEGDVNZfY920aZOysrK0efNmffDBB1q5cqVmzZrl7b9aj/v5PvjgA/Xq1Utt27Yttf/87+s8ceKEJk6cqLS0NG3atElVqlTRPffco+Li4jKNr6znyAsvvKD27dtr165deuyxx/SHP/xB//nPf8p0H7iAwS9WUFBgJJmCggJ/lwJUWoWFhearr74yhYWFv2j7za7NRom67LLZtfnqFm6MGTFihKlataqpVauWz1KjRg0jyRw5csQYY8ywYcNMr169fLZ98sknzc033+y9HR0dbQYOHOizztixY0337t1NcXFxifvetGmTCQ0NNadOnfJpb9q0qXn99deNMcbMnDnTVK1a1eTk5Hj7161bZ6pUqWJyc3ONMcZERkaaP//5zz77uPXWW81jjz1mjDFmz549xmazmR9//NEcPnzYVKtWzcyePdsMHjzYGGPMnDlzTIcOHYwxxhw/ftzUqFHDbNu2zWd/v//9783QoUONMcZs3rzZSDKrVq262MNqjDEmPz/fBAUFmS1btnjb4uPjzZNPPmmMMeajjz4yoaGhxu12X3I/lxIdHW1efPFFs2rVKtO0aVNTXFxslixZYtq2bWuMMSYsLMwsWrTIu74ks3LlSmOMMS6Xy0gyu3btMsYYM2PGDNO7d2+f/efk5BhJZu/evSY9Pd1IMvv27bviOkeMGGHq1q1rTpw44W1buHChqV27tjl79uxVfdzPV6NGDTNu3DiftoEDB3qf5/Hx8RfdNj8/30gyGRkZxpiSj9e5eq70HPntb3/rvV1cXGzCw8PNwoULS63hUn9beP02hhlAAJVa7rHcq7relerWrZt2797ts7z11ls+62RlZXlnyM7p1KmTsrOzfX77uH379j7rJCQkaPfu3WrRooXGjRunjRs3evvS09N1/Phx1atXT7Vr1/YuLpfL59Jbo0aN1LBhQ+/t+Ph4FRcXa+/evXK73frhhx9KrS0rK0uSFBsbq3r16ik1NVWffvqp4uLidPfdd3tnllJSUtSlSxdJP18CPXXqlHr16uVT07vvvlvicuCFY71QgwYN1KtXL73//vuSJJfLpe3bt2v48OGSpF69eik6Olo33HCDHnzwQb3//vu/+KcE77zzTh0/flxbtmzRO++8c8Wzf9LPx2Pz5s0+477xxhslSd9++63i4uLUo0cPtWrVSoMHD9abb76pI0eOlHn/cXFxPl+UHh8fr+PHjysnJ+eqPu4XuvBXeV577TXt3r1bI0eO9Hm8v/32Ww0bNkw33HCDQkND1aRJE0nSgQMHynQ/ZT1Hzp+1tNlscjgcys/Pv6Ix4Wd8CARApeYMcV7V9a5UrVq11KxZM5+277/3fS+iMabEC6kp5dLfhR86aNeunVwul9atW6ePP/5Y999/v3r27Km//e1vKi4ultPp9Lk8e06dOnUuWu+5Os6vp7Tazl+vc+fOSklJUfXq1dW1a1fFxsbq7NmzysjI0LZt2zRhwgRJ8l7uW7Nmja6//nqffdrt9kuOtTTDhw/X+PHj9eqrr2rp0qVq2bKl4uLiJEkhISH64osvlJKSoo0bN+qPf/yjEhMTlZaWdsnxlyYoKEgPPvigZs6cqc8++0wrV668ou2ln8fev39/zZ07t0Sf0+lU1apVlZycrG3btmnjxo169dVXNX36dH322WfesPRL2Gy2q/64nxMTE1Pi8qrT+fN5VLduXZ/2/v37KyoqSm+++aYiIyNVXFys2NhYn/cpXkpZz5Fq1ar53D5//LgyzAACqNRub3S7GoY2lE2l/36wTTZFhUbp9ka3V3Bl/+fmm2/W1q1bfdq2bdum5s2bq2rVqpfcNjQ0VEOGDNGbb76p5cuX66OPPtLhw4fVrl075eXlKSgoSM2aNfNZ6tev793+wIEDPl/XsX37dlWpUkXNmzdXaGioIiMjS63tpptu8t4+9z7AlJQUde3aVTabTbfffruef/55FRYWemdubr75Ztntdh04cKBETVFRUVf8uA0cOFCnTp3S+vXrtXTpUv32t7/16Q8KClLPnj01b9487dmzR/v27dMnn3xyxfcjSSNHjlRqaqoGDBig66677oq3b9eunTIzM9W4ceMSYz8Xumw2mzp16qRZs2Zp165dql69epnD5pdffqnCwkLv7R07dqh27dpq2LDhVX/czxk6dKiSk5O1a9euS6536NAhZWVl6emnn1aPHj100003XdHspvTrzhH8MswAAqjUqlapqpf7vqz7PrxPNtl8PgxyLhS+1PclVa3ivxeRJ554QrfeequeeeYZDRkyRNu3b9eCBQv02muvXXK7F198UU6nU23atFGVKlX017/+VQ6HQ3Xq1FHPnj0VHx+vgQMHau7cuWrRooV++OEHrV27VgMHDvRe6qtRo4ZGjBih559/Xm63W+PGjdP999/v/UqTJ598UjNnzlTTpk3Vpk0bLVq0SLt37/ZeepV+DoDjx49XUFCQbr/9dm/bE088oXbt2ik0NFTSz7NykyZN0uOPP67i4mL95je/kdvt1rZt21S7dm2NGDHiih63WrVqacCAAZoxY4aysrI0bNgwb98///lPfffdd+rcubOuu+46rV27VsXFxWrRooUkacGCBVq5cqU2bdpUpvu66aab9NNPP/3i36MePXq03nzzTQ0dOlRPPvmk6tevr2+++UbLli3Tm2++qZ07d2rTpk3q3bu3wsPD9dlnn+nHH3/0CdqXUlRUpN///vd6+umntX//fs2cOVNjxoxRlSpVrvrjfs7jjz+uNWvWqHv37kpMTNTtt9+u6667Tl9//bXWrVvnDWbXXXed6tWrpzfeeENOp1MHDhwo8Qnuy/ml5wh+OQIggEpv0E2D9Lf7/1bq9wC+1Pclv38PYLt27fThhx/qj3/8o5555hk5nU796U9/UkJCwiW3q127tubOnavs7GxVrVpVt956q9auXev9jdO1a9dq+vTpGjlypH788Uc5HA517txZERER3n00a9ZMgwYN0h133KHDhw/rjjvu8HlRHTdunNxut5544gnl5+fr5ptv1urVqxUTE+NdJzY2VvXr11d0dLQ37HXp0kVnz571vv/vnGeeeUbh4eFKSkrSd999pzp16qhdu3aaNm3aL3rshg8frjvvvFOdO3dWo0aNvO116tTRihUrlJiYqFOnTikmJkYffPCBWrZsKUn66aefrvhrSOrVq/eLapSkyMhI/etf/9KUKVPUp08feTweRUdHq2/fvqpSpYpCQ0O1ZcsWvfTSS3K73YqOjtYLL7xQ5i+W7tGjh2JiYtS5c2d5PB498MADSkxM9PZf7cdd+vl/HjZt2qSXXnpJixYt0tSpU1VcXKwmTZqoX79+evzxxyX9/Ju7y5Yt07hx4xQbG6sWLVrolVdeUdeuXct8X7/0HMEvZzOlXWRHmbjdboWFhamgoMD7RxHAlTl16pRcLpeaNGmiGjVq/Kp9nS0+q08PfKrcY7lyhjh1e6Pb/Trz52+JiYlatWoVP1dWySUkJOjo0aOX/A5ClHSpvy28fjMDCCCAVK1SVV0bd/V3GQBwzeNDIAAA+NH5X91y4XK5X+v4NVq2bHnR+z3/PaAITFwC/hWYQgZ+vat5CRiojL755puL9l1//fUKDg4ul/vdv3+/Tp8+XWpfRESEQkJCyuV+KwqXgC+NS8AAAPjRhd8jWVGio6P9cr+4NnAJGAAAwGIIgAAAABZDAAQAALAYAiAAAIDFEAABAAAshgAIAOXEZrPx6w1+sHjxYtWpU8ffZQDXNAIgAPxCCQkJGjhw4EX7c3Nzy/xbr/5gs9m8S+3atRUXF6fFixf7u6xfbciQIfr666/9XQZwTSMAAkA5cTgcstvtfq3BGKMzZ85ctH/RokXKzc3Vl19+qSFDhuihhx7Shg0byrWmoqKict1/cHCwwsPDy/U+gMrOEgEwKSlJNptNEyZM8LYlJCT4/N+vzWZTx44d/VckgIBz/iXgffv2yWazacWKFerWrZtq1qypuLg4bd++3Webbdu2qXPnzgoODlZUVJTGjRunEydOePvfe+89tW/fXiEhIXI4HBo2bJjy8/O9/SkpKbLZbNqwYYPat28vu91+yZ8Tq1OnjhwOh5o2bapp06apbt262rhxo7e/oKBAjzzyiMLDwxUaGqru3bvryy+/9NnH7NmzFR4erpCQED388MN66qmn1KZNG2//uZnSpKQkRUZGqnnz5pKk//73vxoyZIiuu+461atXTwMGDNC+fft8xvI///M/qlWrlurUqaNOnTpp//79kqQvv/xS3bp1U0hIiEJDQ3XLLbdo586dkkq/BLxw4UI1bdpU1atXV4sWLfS///u/JY7VW2+9pXvuuUc1a9ZUTEyMVq9efdHHDajsAj4ApqWl6Y033lDr1q1L9PXt21e5ubneZe3atX6oEEAJxkgnTvhnKedfx5w+fbomTZqk3bt3q3nz5ho6dKh3hi4jI0N9+vTRoEGDtGfPHi1fvlxbt27VmDFjvNsXFRXpmWee0ZdffqlVq1bJ5XIpISGhxP1MnjxZSUlJysrKKvXv34XOnj2rDz/8UIcPH1a1atUk/Tx7eOeddyovL09r165Venq62rVrpx49eujw4cOSpPfff19//vOfNXfuXKWnp6tRo0ZauHBhif1v2rRJWVlZSk5O1j//+U+dPHlS3bp1U+3atbVlyxZt3bpVtWvXVt++fVVUVKQzZ85o4MCB6tKli/bs2aPt27frkUcekc1mkyQNHz5cDRs2VFpamtLT0/XUU095677QypUrNX78eD3xxBP697//rUcffVQPPfSQNm/e7LPerFmzdP/992vPnj264447NHz4cO84gYBjAtixY8dMTEyMSU5ONl26dDHjx4/39o0YMcIMGDDgV+2/oKDASDIFBQW/rlDAwgoLC81XX31lCgsL/6/x+HFjfo5iFb8cP17m2i/3d0SSWblypTHGGJfLZSSZt956y9ufmZlpJJmsrCxjjDEPPvigeeSRR3z28emnn5oqVar4Pj7n+fzzz40kc+zYMWOMMZs3bzaSzKpVqy5bvyRTo0YNU6tWLVO1alUjydStW9dkZ2cbY4zZtGmTCQ0NNadOnfLZrmnTpub11183xhjToUMHM3r0aJ/+Tp06mbi4OO/tESNGmIiICOPxeLxtb7/9tmnRooUpLi72tnk8HhMcHGw2bNhgDh06ZCSZlJSUUmsPCQkxixcvLrVv0aJFJiwszHv7tttuM6NGjfJZZ/DgweaOO+7weSyefvpp7+3jx48bm81m1q1bV+p94NpX6t+W/x+v38YE9Azg6NGjdeedd6pnz56l9qekpCg8PFzNmzfXqFGjfC6jAEB5OH82zul0SpL3b096eroWL16s2rVre5c+ffqouLhYLpdLkrRr1y4NGDBA0dHRCgkJUdeuXSVJBw4c8Lmf9u3bl6meF198Ubt371ZycrLatGmjF1980fvbtOnp6Tp+/Ljq1avnU5PL5dK3334rSdq7d6/+53/+x2efF96WpFatWql69ere2+np6frmm28UEhLi3W/dunV16tQpffvtt6pbt64SEhLUp08f9e/fXy+//LJyc3O920+cOFEPP/ywevbsqWeffdZbT2mysrLUqVMnn7ZOnTopKyvLp+38Y1OrVi2FhITwuoCAFeTvAsrLsmXL9MUXXygtLa3U/n79+mnw4MGKjo6Wy+XSjBkz1L17d6Wnp1/0Tdsej0cej8d72+12l0vtgOXVrCkdP+6/+y5H51+mPHc5s7i42PvfRx99VOPGjSuxXaNGjXTixAn17t1bvXv31nvvvacGDRrowIED6tOnT4kPVtSqVatM9TgcDjVr1kzNmjXTX//6V7Vt21bt27fXzTffrOLiYjmdTqWkpJTY7vz32J0bxzmmlMvoF9ZTXFysW265Re+//36JdRs0aCDp5w+ojBs3TuvXr9fy5cv19NNPKzk5WR07dlRiYqKGDRumNWvWaN26dZo5c6aWLVume+65p9RxllbjhW0XXkK22WzeYwMEmoAMgDk5ORo/frw2btyoGjVqlLrOkCFDvP+OjY1V+/btFR0drTVr1mjQoEGlbpOUlKRZs2aVS80AzmOzSWUMMIGkXbt2yszM9M7AXSgjI0M//fSTnn32WUVFRUmS94MPV0OzZs107733aurUqfr73/+udu3aKS8vT0FBQWrcuHGp27Ro0UKff/65HnzwQW9bWWpq166dli9f7v1wycW0bdtW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", "text/html": [ "\n", "
\n", "
\n", " Figure\n", "
\n", " \n", "
\n", " " ], "text/plain": [ "Canvas(toolbar=Toolbar(toolitems=[('Home', 'Reset original view', 'home', 'home'), ('Back', 'Back to previous …" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "x_data = []\n", "y_data = []\n", "\n", "fig, ax = plt.subplots() # A tuple unpacking to unpack the only fig\n", "ax.set_xlim(30, 250)\n", "ax.set_ylim(5, 50)\n", "# Plotting \n", "scatter, = ax.plot([], [], 'go', label='Horsepower vs. Miles_per_Gallon')\n", "line, = ax.plot([], [], 'r', label='Linear Regression')\n", "ax.legend()\n", "\n", "reg = LinearRegression()\n", "\n", "def animate(frame_num):\n", " # Adding data\n", " x_data.append(x[frame_num])\n", " y_data.append(y[frame_num])\n", " # Convert data to numpy array\n", " x_train = np.array(x_data).reshape(-1, 1)\n", " y_train = np.array(y_data).reshape(-1, 1)\n", " # Fit values to a linear regression\n", " reg.fit(x_train, y_train)\n", "\n", " # update data for scatter plot\n", " scatter.set_data((x_data, y_data))\n", " # Predict value and update data for line plot\n", " line.set_data((list(range(250)), reg.predict(np.array([entry for entry in range(250)]).reshape(-1, 1))))\n", "\n", "anim = FuncAnimation(fig, animate, frames=len(x), interval=20)\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3. Animations with embedded HTML5 video" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "from matplotlib.animation import FuncAnimation\n", "from IPython import display\n", "\n", "# Turn off matplotlib plot in Notebook\n", "plt.ioff()\n", "# Pass the ffmpeg path\n", "plt.rcParams['animation.ffmpeg_path'] = '/usr/local/bin/ffmpeg'" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/opt/conda/lib/python3.10/site-packages/matplotlib/animation.py:879: UserWarning: Animation was deleted without rendering anything. This is most likely not intended. To prevent deletion, assign the Animation to a variable, e.g. `anim`, that exists until you output the Animation using `plt.show()` or `anim.save()`.\n", " warnings.warn(\n" ] }, { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "x_data = []\n", "y_data = []\n", "\n", "fig, ax = plt.subplots()\n", "ax.set_xlim(30, 250)\n", "ax.set_ylim(5, 50)\n", "scatter, = ax.plot([], [], 'go', label='Horsepower vs. Miles_per_Gallon')\n", "line, = ax.plot([], [], 'r', label='Linear Regression')\n", "ax.legend()\n", "\n", "reg = LinearRegression()\n", "\n", "def animate(frame_num):\n", " # Adding data\n", " x_data.append(x[frame_num])\n", " y_data.append(y[frame_num])\n", " # Convert data to numpy array\n", " x_train = np.array(x_data).reshape(-1, 1)\n", " y_train = np.array(y_data).reshape(-1, 1)\n", " reg.fit(x_train, y_train)\n", " \n", " # update data for scatter plot\n", " scatter.set_data((x_data, y_data))\n", " # Predict value and update data for line plot\n", " line.set_data((list(range(250)), reg.predict(np.array([entry for entry in range(250)]).reshape(-1, 1))))\n", "\n", "anim = FuncAnimation(fig, animate, frames=len(x), interval=20)\n", "\n", "video = anim.to_html5_video()\n", "html = display.HTML(video)\n", "display.display(html)\n", "plt.close()\n", "\n", "# Note Github only render static HTML and the embeded HTML5 video won't be displayed, \n", "# The embedded video should be working if you host the Notebook or open it locally. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Thanks for reading\n", "\n", "This is a notebook for the medium article [Matplotlib Linear Regression Animation in Jupyter Notebook](https://bindichen.medium.com/matplotlib-linear-regression-animation-in-jupyter-notebook-2435b711bea2)\n", "\n", "**License**: [BSD 2-Clause](https://opensource.org/licenses/BSD-2-Clause)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "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.6" } }, "nbformat": 4, "nbformat_minor": 4 }