commit 7d9352968d2050021cce4ea5032e4ccbeb479805 Author: Steve Kossouho Date: Fri Jul 4 19:58:11 2025 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..807c92a --- /dev/null +++ b/.gitignore @@ -0,0 +1,166 @@ +### Python template +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +/documentation/slides/ +.idea diff --git a/display-demo.ipynb b/display-demo.ipynb new file mode 100644 index 0000000..19629da --- /dev/null +++ b/display-demo.ipynb @@ -0,0 +1,137 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "4a86ee8c-7c0b-4c22-af77-ed3047d9dc22", + "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", + "
productpricewpu
0pomme1.99200
1poire2.49180
2banane2.99140
3pêche3.49200
\n", + "
" + ], + "text/plain": [ + " product price wpu\n", + "0 pomme 1.99 200\n", + "1 poire 2.49 180\n", + "2 banane 2.99 140\n", + "3 pêche 3.49 200" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAiMAAAHXCAYAAABuwVojAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAAA9hAAAPYQGoP6dpAAAv8klEQVR4nO3deVRV9f7/8dcR9YgKxwEZ1KNQKo44YCSaaWU5VfK769pwLdClWDcxjQa/3FVa2ndhmWLf9Dqkhg1mk2llZl4Nvc6iYmjmVIoaqFmKkIHA+f3h8tzOZcij4OcAz8dae632Z38+Z78Pp4Mv9v7svS0Oh8MhAAAAQ2qYLgAAAFRvhBEAAGAUYQQAABhFGAEAAEYRRgAAgFGEEQAAYBRhBAAAGFXTdAFXo6ioSD/99JN8fHxksVhMlwMAAK6Cw+HQhQsX1LRpU9WoUfrxj0oRRn766SfZ7XbTZQAAgGtw/PhxNW/evNTtlSKM+Pj4SLr8Znx9fQ1XAwAArkZ2drbsdrvz3/HSVIowcuXUjK+vL2EEAIBK5s+mWDCBFQAAGEUYAQAARhFGAACAUZVizsjVKCoqUn5+vukyKr3atWuXefkVAADlrUqEkfz8fP34448qKioyXUqlV6NGDYWEhKh27dqmSwEAVBOVPow4HA5lZmbKy8tLdrudv+qvw5Wby2VmZqpFixbcYA4AcENU+jBSUFCg3377TU2bNlXdunVNl1PpNWnSRD/99JMKCgpUq1Yt0+UAAKqBSn8YobCwUJI4rVBOrvwcr/xcAQCoaJU+jFzBKYXywc8RAHCjVZkwAgAAKie3wsicOXMUFhbmvC17ZGSkVq1aVWr/5ORkWSwWl6VOnTrXXTQAAKg63JrA2rx5c02dOlWtW7eWw+HQ4sWLNWTIEO3evVsdOnQocYyvr68OHDjgXL9RpwGC/2flDdnPFUenDq74fRw9qpCQEO3evVtdunSp8P0BAHAjuBVG7rvvPpf1//3f/9WcOXO0devWUsOIxWJRYGDgtVcIJ7vdrszMTPn5+ZkuBQCAcnPNc0YKCwu1dOlS5ebmKjIystR+OTk5atmypex2u4YMGaJ9+/b96Wvn5eUpOzvbZanu8vPz5eXlpcDAQNWsWemvyAYAwMntMJKenq769evLarXq8ccf16effqr27duX2Dc0NFSLFi3SihUr9O6776qoqEg9e/bUiRMnytxHYmKibDabc7Hb7e6W6fH69u2ruLg4xcXFyWazyc/PTy+88IIcDockKTg4WFOmTFF0dLR8fX01evRoHT16VBaLRWlpac7X2bdvn+699175+vrKx8dHvXv31pEjR5zbFyxYoHbt2qlOnTpq27at/vnPf97otwoAQJnc/hM7NDRUaWlpOn/+vD7++GPFxMRo/fr1JQaSyMhIl6MmPXv2VLt27TRv3jxNmTKl1H0kJCQoPj7euZ6dnV0lA8nixYs1cuRIbd++XampqRo9erRatGih2NhYSdJrr72miRMnatKkSSWOP3nypG6//Xb17dtX69atk6+vrzZt2qSCggJJ0nvvvaeJEydq1qxZ6tq1q3bv3q3Y2FjVq1dPMTExN+x9AoCnuNHzCT3FjZjXeD3cDiO1a9dWq1atJEnh4eHasWOHXn/9dc2bN+9Px9aqVUtdu3bV4cOHy+xntVpltVrdLa3SsdvtSkpKksViUWhoqNLT05WUlOQMI3feeaeefvppZ/+jR4+6jJ89e7ZsNpuWLl3qvFtqmzZtnNsnTZqk6dOn6y9/+YskKSQkRN99953mzZtHGAEAeIzrvs9IUVGR8vLyrqpvYWGh0tPTFRQUdL27rRJ69OjhcnVRZGSkDh065Lz7affu3cscn5aWpt69e5d42/bc3FwdOXJEI0eOVP369Z3Lyy+/7HIaBwAA09w6MpKQkKCBAweqRYsWunDhgpYsWaKUlBStXr1akhQdHa1mzZopMTFRkjR58mT16NFDrVq10rlz5zRt2jQdO3ZMo0aNKv93UgXVq1evzO3e3t6lbsvJyZEkvfnmm7r11ltdtnl5eV1/cQAAlBO3wsjp06cVHR2tzMxM2Ww2hYWFafXq1br77rslSRkZGS5Pzf31118VGxurrKwsNWzYUOHh4dq8eXOpE16rm23btrmsb926Va1bt77qsBAWFqbFixfr0qVLxY6OBAQEqGnTpvrhhx80bNiwcqsZAIDy5lYYWbhwYZnbU1JSXNaTkpKUlJTkdlHVRUZGhuLj4/XYY49p165deuONNzR9+vSrHh8XF6c33nhDDz30kBISEmSz2bR161ZFREQoNDRUL730kp588knZbDYNGDBAeXl5Sk1N1a+//uoyQRgAAJOq7A0rPH3msHT5tNbFixcVEREhLy8vjRs3TqNHj77q8Y0bN9a6dev07LPPqk+fPvLy8lKXLl3Uq1cvSdKoUaNUt25dTZs2Tc8++6zq1aunTp06afz48RX0jgAAcF+VDSOVQa1atTRz5kzNmTOn2Lb/vnJGunzvkSv3Ibniyqmy0vztb3/T3/72t+uuFQCAisJTewEAgFGEEQAAYBSnaQz578m+AABUVxwZAQAARlWZMPLfEztxbfg5AgButEp/mqZWrVqyWCw6c+aMmjRp4nJ7dbjH4XDozJkzslgsJd5iHgCAilDpw4iXl5eaN2+uEydOlHg5LNxjsVjUvHlzbhkPALhhKn0YkaT69eurdevWunTpkulSKr1atWoRRAAAN1SVCCPS5SMk/CMKAEDlU2UmsAIAgMqJMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMMqtMDJnzhyFhYXJ19dXvr6+ioyM1KpVq8oc89FHH6lt27aqU6eOOnXqpC+//PK6CgYAAFWLW2GkefPmmjp1qnbu3KnU1FTdeeedGjJkiPbt21di/82bN+vhhx/WyJEjtXv3bkVFRSkqKkp79+4tl+IBAEDlZ3E4HI7reYFGjRpp2rRpGjlyZLFtDz74oHJzc/XFF18423r06KEuXbpo7ty5pb5mXl6e8vLynOvZ2dmy2+06f/68fH19r6dcAEA1Fvw/K02XYMTRqYON7Dc7O1s2m+1P//2+5jkjhYWFWrp0qXJzcxUZGVliny1btqhfv34ubf3799eWLVvKfO3ExETZbDbnYrfbr7VMAADg4Wq6OyA9PV2RkZH6/fffVb9+fX366adq3759iX2zsrIUEBDg0hYQEKCsrKwy95GQkKD4+Hjn+pUjIwBQ3vhLGTDP7TASGhqqtLQ0nT9/Xh9//LFiYmK0fv36UgPJtbBarbJareX2egAAwHO5HUZq166tVq1aSZLCw8O1Y8cOvf7665o3b16xvoGBgTp16pRL26lTpxQYGHiN5QIAgKrmuu8zUlRU5DLZ9I8iIyO1du1al7Y1a9aUOscEAABUP24dGUlISNDAgQPVokULXbhwQUuWLFFKSopWr14tSYqOjlazZs2UmJgoSRo3bpz69Omj6dOna/DgwVq6dKlSU1M1f/788n8nAACgUnIrjJw+fVrR0dHKzMyUzWZTWFiYVq9erbvvvluSlJGRoRo1/nOwpWfPnlqyZImef/55/eMf/1Dr1q21fPlydezYsXzfBQAAqLTcCiMLFy4sc3tKSkqxtqFDh2ro0KFuFQUAAKoPnk0DAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAot8JIYmKibrnlFvn4+Mjf319RUVE6cOBAmWOSk5NlsVhcljp16lxX0QAAoOpwK4ysX79eY8aM0datW7VmzRpdunRJ99xzj3Jzc8sc5+vrq8zMTOdy7Nix6yoaAABUHTXd6fzVV1+5rCcnJ8vf3187d+7U7bffXuo4i8WiwMDAa6sQAABUadc1Z+T8+fOSpEaNGpXZLycnRy1btpTdbteQIUO0b9++Mvvn5eUpOzvbZQEAAFXTNYeRoqIijR8/Xr169VLHjh1L7RcaGqpFixZpxYoVevfdd1VUVKSePXvqxIkTpY5JTEyUzWZzLna7/VrLBAAAHs6t0zR/NGbMGO3du1cbN24ss19kZKQiIyOd6z179lS7du00b948TZkypcQxCQkJio+Pd65nZ2cTSHDDBP/PStMlGHF06mDTJQCopq4pjMTFxemLL77Qhg0b1Lx5c7fG1qpVS127dtXhw4dL7WO1WmW1Wq+lNAAAUMm4dZrG4XAoLi5On376qdatW6eQkBC3d1hYWKj09HQFBQW5PRYAAFQ9bh0ZGTNmjJYsWaIVK1bIx8dHWVlZkiSbzSZvb29JUnR0tJo1a6bExERJ0uTJk9WjRw+1atVK586d07Rp03Ts2DGNGjWqnN8KAACojNwKI3PmzJEk9e3b16X9rbfe0vDhwyVJGRkZqlHjPwdcfv31V8XGxiorK0sNGzZUeHi4Nm/erPbt219f5QAAoEpwK4w4HI4/7ZOSkuKynpSUpKSkJLeKAgAA1QfPpgEAAEYRRgAAgFGEEQAAYBRhBAAAGEUYAQAARhFGAACAUYQRAABgFGEEAAAYRRgBAABGEUYAAIBRhBEAAGAUYQQAABhFGAEAAEYRRgAAgFGEEQAAYBRhBAAAGEUYAQAARhFGAACAUYQRAABgFGEEAAAYRRgBAABGEUYAAIBRhBEAAGAUYQQAABhFGAEAAEYRRgAAgFGEEQAAYBRhBAAAGEUYAQAARhFGAACAUYQRAABgFGEEAAAYRRgBAABGEUYAAIBRhBEAAGAUYQQAABhFGAEAAEYRRgAAgFGEEQAAYJRbYSQxMVG33HKLfHx85O/vr6ioKB04cOBPx3300Udq27at6tSpo06dOunLL7+85oIBAEDV4lYYWb9+vcaMGaOtW7dqzZo1unTpku655x7l5uaWOmbz5s16+OGHNXLkSO3evVtRUVGKiorS3r17r7t4AABQ+dV0p/NXX33lsp6cnCx/f3/t3LlTt99+e4ljXn/9dQ0YMEDPPvusJGnKlClas2aNZs2apblz515j2QAAoKq4rjkj58+flyQ1atSo1D5btmxRv379XNr69++vLVu2lDomLy9P2dnZLgsAAKiarjmMFBUVafz48erVq5c6duxYar+srCwFBAS4tAUEBCgrK6vUMYmJibLZbM7Fbrdfa5kAAMDDuXWa5o/GjBmjvXv3auPGjeVZjyQpISFB8fHxzvXs7GyjgST4f1Ya27dJR6cONl0CAKAauKYwEhcXpy+++EIbNmxQ8+bNy+wbGBioU6dOubSdOnVKgYGBpY6xWq2yWq3XUhoAAKhk3DpN43A4FBcXp08//VTr1q1TSEjIn46JjIzU2rVrXdrWrFmjyMhI9yoFAABVkltHRsaMGaMlS5ZoxYoV8vHxcc77sNls8vb2liRFR0erWbNmSkxMlCSNGzdOffr00fTp0zV48GAtXbpUqampmj9/fjm/FQAAUBm5dWRkzpw5On/+vPr27augoCDn8sEHHzj7ZGRkKDMz07nes2dPLVmyRPPnz1fnzp318ccfa/ny5WVOegUAANWHW0dGHA7Hn/ZJSUkp1jZ06FANHTrUnV0BAIBqgmfTAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMcjuMbNiwQffdd5+aNm0qi8Wi5cuXl9k/JSVFFoul2JKVlXWtNQMAgCrE7TCSm5urzp07a/bs2W6NO3DggDIzM52Lv7+/u7sGAABVUE13BwwcOFADBw50e0f+/v5q0KCB2+MAAEDVdsPmjHTp0kVBQUG6++67tWnTpjL75uXlKTs722UBAABVU4WHkaCgIM2dO1effPKJPvnkE9ntdvXt21e7du0qdUxiYqJsNptzsdvtFV0mAAAwxO3TNO4KDQ1VaGioc71nz546cuSIkpKS9M4775Q4JiEhQfHx8c717OxsAgkAAFVUhYeRkkRERGjjxo2lbrdarbJarTewIgAAYIqR+4ykpaUpKCjIxK4BAICHcfvISE5Ojg4fPuxc//HHH5WWlqZGjRqpRYsWSkhI0MmTJ/X2229LkmbOnKmQkBB16NBBv//+uxYsWKB169bp66+/Lr93AQAAKi23w0hqaqruuOMO5/qVuR0xMTFKTk5WZmamMjIynNvz8/P19NNP6+TJk6pbt67CwsL0r3/9y+U1AABA9eV2GOnbt68cDkep25OTk13Wn3vuOT333HNuFwYAAKoHnk0DAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAot8PIhg0bdN9996lp06ayWCxavnz5n45JSUlRt27dZLVa1apVKyUnJ19DqQAAoCpyO4zk5uaqc+fOmj179lX1//HHHzV48GDdcccdSktL0/jx4zVq1CitXr3a7WIBAEDVU9PdAQMHDtTAgQOvuv/cuXMVEhKi6dOnS5LatWunjRs3KikpSf3793d39wAAoIqp8DkjW7ZsUb9+/Vza+vfvry1btpQ6Ji8vT9nZ2S4LAAComio8jGRlZSkgIMClLSAgQNnZ2bp48WKJYxITE2Wz2ZyL3W6v6DIBAIAhHnk1TUJCgs6fP+9cjh8/brokAABQQdyeM+KuwMBAnTp1yqXt1KlT8vX1lbe3d4ljrFarrFZrRZcGAAA8QIUfGYmMjNTatWtd2tasWaPIyMiK3jUAAKgE3A4jOTk5SktLU1pamqTLl+6mpaUpIyND0uVTLNHR0c7+jz/+uH744Qc999xz+v777/XPf/5TH374oZ566qnyeQcAAKBSczuMpKamqmvXrurataskKT4+Xl27dtXEiRMlSZmZmc5gIkkhISFauXKl1qxZo86dO2v69OlasGABl/UCAABJ1zBnpG/fvnI4HKVuL+nuqn379tXu3bvd3RUAAKgGPPJqGgAAUH0QRgAAgFGEEQAAYBRhBAAAGEUYAQAARhFGAACAUYQRAABgFGEEAAAYRRgBAABGEUYAAIBRhBEAAGAUYQQAABhFGAEAAEYRRgAAgFGEEQAAYBRhBAAAGEUYAQAARhFGAACAUYQRAABgFGEEAAAYRRgBAABGEUYAAIBRhBEAAGAUYQQAABhFGAEAAEYRRgAAgFGEEQAAYBRhBAAAGEUYAQAARhFGAACAUYQRAABgFGEEAAAYRRgBAABGEUYAAIBRhBEAAGAUYQQAABhFGAEAAEYRRgAAgFGEEQAAYBRhBAAAGHVNYWT27NkKDg5WnTp1dOutt2r79u2l9k1OTpbFYnFZ6tSpc80FAwCAqsXtMPLBBx8oPj5ekyZN0q5du9S5c2f1799fp0+fLnWMr6+vMjMzncuxY8euq2gAAFB1uB1GZsyYodjYWI0YMULt27fX3LlzVbduXS1atKjUMRaLRYGBgc4lICCgzH3k5eUpOzvbZQEAAFWTW2EkPz9fO3fuVL9+/f7zAjVqqF+/ftqyZUup43JyctSyZUvZ7XYNGTJE+/btK3M/iYmJstlszsVut7tTJgAAqETcCiM///yzCgsLix3ZCAgIUFZWVoljQkNDtWjRIq1YsULvvvuuioqK1LNnT504caLU/SQkJOj8+fPO5fjx4+6UCQAAKpGaFb2DyMhIRUZGOtd79uypdu3aad68eZoyZUqJY6xWq6xWa0WXBgAAPIBbR0b8/Pzk5eWlU6dOubSfOnVKgYGBV/UatWrVUteuXXX48GF3dg0AAKoot8JI7dq1FR4errVr1zrbioqKtHbtWpejH2UpLCxUenq6goKC3KsUAABUSW6fpomPj1dMTIy6d++uiIgIzZw5U7m5uRoxYoQkKTo6Ws2aNVNiYqIkafLkyerRo4datWqlc+fOadq0aTp27JhGjRpVvu8EAABUSm6HkQcffFBnzpzRxIkTlZWVpS5duuirr75yTmrNyMhQjRr/OeDy66+/KjY2VllZWWrYsKHCw8O1efNmtW/fvvzeBQAAqLSuaQJrXFyc4uLiStyWkpLisp6UlKSkpKRr2Q0AAKgGeDYNAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjrimMzJ49W8HBwapTp45uvfVWbd++vcz+H330kdq2bas6deqoU6dO+vLLL6+pWAAAUPW4HUY++OADxcfHa9KkSdq1a5c6d+6s/v376/Tp0yX237x5sx5++GGNHDlSu3fvVlRUlKKiorR3797rLh4AAFR+boeRGTNmKDY2ViNGjFD79u01d+5c1a1bV4sWLSqx/+uvv64BAwbo2WefVbt27TRlyhR169ZNs2bNuu7iAQBA5VfTnc75+fnauXOnEhISnG01atRQv379tGXLlhLHbNmyRfHx8S5t/fv31/Lly0vdT15envLy8pzr58+flyRlZ2e7U265Kcr7zch+TTP18zaNz7t64fOuXvi8zezX4XCU2c+tMPLzzz+rsLBQAQEBLu0BAQH6/vvvSxyTlZVVYv+srKxS95OYmKiXXnqpWLvdbnenXFwn20zTFeBG4vOuXvi8qxfTn/eFCxdks9lK3e5WGLlREhISXI6mFBUV6ZdfflHjxo1lsVgMVnZjZWdny2636/jx4/L19TVdDioYn3f1wuddvVTXz9vhcOjChQtq2rRpmf3cCiN+fn7y8vLSqVOnXNpPnTqlwMDAEscEBga61V+SrFarrFarS1uDBg3cKbVK8fX1rVb/81Z3fN7VC5939VIdP++yjohc4dYE1tq1ays8PFxr1651thUVFWnt2rWKjIwscUxkZKRLf0las2ZNqf0BAED14vZpmvj4eMXExKh79+6KiIjQzJkzlZubqxEjRkiSoqOj1axZMyUmJkqSxo0bpz59+mj69OkaPHiwli5dqtTUVM2fP7983wkAAKiU3A4jDz74oM6cOaOJEycqKytLXbp00VdffeWcpJqRkaEaNf5zwKVnz55asmSJnn/+ef3jH/9Q69attXz5cnXs2LH83kUVZbVaNWnSpGKnrFA18XlXL3ze1Qufd9ksjj+73gYAAKAC8WwaAABgFGEEAAAYRRgBAABGEUYAAIBRhBEAAGAUYQQAABhFGPFQ+fn5OnDggAoKCkyXAqAcFRQU6F//+pfmzZunCxcuSJJ++ukn5eTkGK4MMIcw4mF+++03jRw5UnXr1lWHDh2UkZEhSRo7dqymTp1quDpUhHPnzmnBggVKSEjQL7/8IknatWuXTp48abgylLdjx46pU6dOGjJkiMaMGaMzZ85Ikl555RU988wzhqtDReD7fXUIIx4mISFBe/bsUUpKiurUqeNs79evnz744AODlaEifPvtt2rTpo1eeeUVvfbaazp37pwkadmyZUpISDBbHMrduHHj1L17d/3666/y9vZ2tv+///f/ij3DC5Uf3++rRxjxMMuXL9esWbN02223yWKxONs7dOigI0eOGKwMFSE+Pl7Dhw/XoUOHXMLnoEGDtGHDBoOVoSL8+9//1vPPP6/atWu7tAcHB/OXchXE9/vqEUY8zJkzZ+Tv71+sPTc31yWcoGrYsWOHHnvssWLtzZo1U1ZWloGKUJGKiopUWFhYrP3EiRPy8fExUBEqEt/vq0cY8TDdu3fXypUrnetXAsiCBQsUGRlpqixUEKvVquzs7GLtBw8eVJMmTQxUhIp0zz33aObMmc51i8WinJwcTZo0SYMGDTJXGCoE3++rx4PyPMzGjRs1cOBAPfLII0pOTtZjjz2m7777Tps3b9b69esVHh5uukSUo1GjRuns2bP68MMP1ahRI3377bfy8vJSVFSUbr/9dpd/uFD5nThxQv3795fD4dChQ4fUvXt3HTp0SH5+ftqwYUOJR0VRefH9vnqEEQ905MgRTZ06VXv27FFOTo66deumCRMmqFOnTqZLQzk7f/68/vrXvyo1NVUXLlxQ06ZNlZWVpcjISH355ZeqV6+e6RJRzgoKCrR06VJ9++23zu/3sGHDXCa0omrg+331CCOAB9i0aZNL+OzXr5/pkgCUk40bN7qET77fxRFGPNTp06d1+vRpFRUVubSHhYUZqgjl7dKlS/L29lZaWpo6duxouhzcIIcOHdI333xT4vd74sSJhqoCzKppugC42rlzp2JiYrR//379d060WCwlzsRH5VSrVi21aNGCz7QaefPNN/X3v/9dfn5+CgwMdLlCzmKxEEaqoLVr12rt2rUlhs9FixYZqsrzcGTEw3Tu3Fk333yzJkyYoICAgGKX87Zs2dJQZagICxcu1LJly/TOO++oUaNGpstBBWvZsqWeeOIJTZgwwXQpuAFeeuklTZ48Wd27d1dQUFCx3+effvqpoco8D2HEw/j4+Gj37t1q1aqV6VJwA3Tt2lWHDx/WpUuX1LJly2IT2nbt2mWoMlQEX19fpaWl6aabbjJdCm6AoKAgvfrqq3r00UdNl+LxOE3jYe666y7t2bOHMFJNREVFmS4BN9DQoUP19ddf6/HHHzddCm6A/Px89ezZ03QZlQJHRjzMzz//rJiYGEVERKhjx46qVauWy/b777/fUGUArldiYqJmzJihwYMHq1OnTsW+308++aShylARJkyYoPr16+uFF14wXYrHI4x4mM8//1yPPvpoiXftYwIrULmFhISUus1iseiHH364gdWgIsTHxzv/u6ioSIsXL1ZYWJjCwsKKhc8ZM2bc6PI8FmHEwwQHB+vee+/VCy+8oICAANPloAI0atRIBw8elJ+fnxo2bFjmM4euPHIcQOVwxx13XFU/i8WidevWVXA1lQdzRjzM2bNn9dRTTxFEqrCkpCTnQ9G4HTRQtXzzzTemS6iUODLiYWJiYtS7d2+NGjXKdCkAKsCJEyf02WefKSMjQ/n5+S7bOGxftZw/f16FhYXFLtv/5ZdfVLNmTfn6+hqqzPNwZMTDtGnTRgkJCdq4cSMT3KqJwsJCLV++XPv375ckdejQQffff7+8vLwMV4bytnbtWt1///266aab9P3336tjx446evSoHA6HunXrZro8lLOHHnpI9913n5544gmX9g8//FCfffaZvvzyS0OVeR6OjHgYJrhVL4cPH9agQYN08uRJhYaGSpIOHDggu92ulStX6uabbzZcIcpTRESEBg4cqJdeekk+Pj7as2eP/P39NWzYMA0YMEB///vfTZeIctSoUSNt2rRJ7dq1c2n//vvv1atXL509e9ZQZZ6HMAIYNGjQIDkcDr333nvOQ7lnz57VI488oho1amjlypWGK0R58vHxUVpamm6++WY1bNhQGzduVIcOHbRnzx4NGTJER48eNV0iylG9evW0devWYk9cT09P16233qrffvvNUGWep4bpAoDqbP369Xr11Vddzik3btxYU6dO1fr16w1WhopQr1495zyRoKAgHTlyxLnt559/NlUWKkhERITmz59frH3u3LkKDw83UJHnYs6Ih3E4HPr4449LfarnsmXLDFWGimC1WnXhwoVi7Tk5Oapdu7aBilCRevTooY0bN6pdu3YaNGiQnn76aaWnp2vZsmXq0aOH6fJQzl5++WX169dPe/bs0V133SXp8ryhHTt26OuvvzZcnWfhyIiHGT9+vB599FH9+OOPql+/vmw2m8uCquXee+/V6NGjtW3bNjkcDjkcDm3dulWPP/44d9utgmbMmKFbb71V0uWHqN1111364IMPFBwcrIULFxquDuWtV69e2rJli5o3b64PP/xQn3/+uVq1aqVvv/1WvXv3Nl2eR2HOiIdp1KiR3n33XQ0aNMh0KbgBzp07p5iYGH3++efOK6cuXbqkIUOGKDk5mQAKoFrgNI2HsdlsPNGzGmnQoIFWrFihw4cP67vvvpMktW/fngclVnH5+fklnoZt0aKFoYpQUY4cOaK33npLP/zwg2bOnCl/f3+tWrVKLVq0UIcOHUyX5zE4TeNhXnzxRb300ku6ePGi6VJwgyxcuFBRUVEaOnSohg4dqqioKC1YsMB0WagABw8eVO/eveXt7a2WLVsqJCREISEhCg4OLvOyflQux48fl3R5gnqnTp20bds2ffLJJ8rJyZEk7dmzR5MmTTJZosfhyIiHeeCBB/T+++/L399fwcHBxW56tmvXLkOVoSJMnDhRM2bM0NixYxUZGSlJ2rJli5566illZGRo8uTJhitEeRoxYoRq1qypL774QkFBQWU+lwiV09y5c7Vw4ULt2LFDEyZM0Msvv6z4+HjnIyAk6c4779SsWbMMVul5mDPiYR544AF98803+utf/6qAgIBiv6xI01VLkyZN9H//9396+OGHXdrff/99jR07lss9q5h69epp586datu2relSUAFmzJihVatWafny5apXr57q16+v9PR0hYSEOG9yd9NNN+no0aNq27atfv/9d9MlewyOjHiYlStXavXq1brttttMl4Ib4NKlS+revXux9vDwcBUUFBioCBWpffv2BMwq7LbbbtObb76pJUuWKDY2Vg0aNFBmZmaxU3C7d+9Ws2bNDFXpmZgz4mHsdjsPT6pGHn30Uc2ZM6dY+/z58zVs2DADFaEivfLKK3ruueeUkpKis2fPKjs722VB5RYREaHt27dr9+7dki4/m2bChAnKysqSxWJRUVGRNm3apGeeeUbR0dGGq/UsnKbxMCtXrtQbb7yhuXPnKjg42HQ5qGBjx47V22+/Lbvd7rzp1bZt25SRkaHo6GiXOUM80bXyq1Hj8t9//3361eFwyGKxqLCw0ERZqCD5+fkaM2aMkpOTVVhYqJo1a6qgoEDDhg1TcnIyD8P8A8KIh2nYsKF+++03FRQUqG7dusUmsP7yyy+GKkNFuOOOO66qn8Vi0bp16yq4GlS0P7vFf58+fW5QJbiRjh8/rvT0dOXm5qpr165cul8CwoiHWbx4cZnbY2JiblAlAIDrtXDhQiUlJenQoUOSpNatW2v8+PEaNWqU4co8C2EEAG6w3377TRkZGc6H5l0RFhZmqCJUhNIu3Z81a5aeeuopLt3/A8KIByosLNTy5cu1f/9+SVKHDh10//33c34RqOTOnDmjESNGaNWqVSVuZ85I1cKl+1ePq2k8zOHDh9WuXTtFR0dr2bJlWrZsmR555BF16NDB5XHjACqf8ePH69y5c9q2bZu8vb311VdfafHixWrdurU+++wz0+WhnHHp/tXjyIiHGTRokBwOh9577z01atRIknT27Fk98sgjqlGjhlauXGm4QgDXKigoSCtWrFBERIR8fX2VmpqqNm3a6LPPPtOrr76qjRs3mi4R5Wjs2LGqVatWsSvhnnnmGV28eFGzZ882VJnn4aZnHmb9+vXaunWrM4hIUuPGjTV16lT16tXLYGUArldubq78/f0lXb5y7syZM2rTpo06derEox6qqIULF+rrr78u8dL9+Ph4Z7/qfuk+YcTDWK1WXbhwoVh7Tk6OateubaAiAOUlNDRUBw4cUHBwsDp37qx58+YpODhYc+fOVVBQkOnyUM727t2rbt26SZLzNLufn5/8/Py0d+9eZz+eUcRpGo8THR2tXbt2aeHChYqIiJB0OUnHxsYqPDxcycnJZgsEcM3effddFRQUaPjw4dq5c6cGDBigs2fPqnbt2lq8eLEefPBB0yUCRhBGPMy5c+cUExOjzz//3HnDs0uXLmnIkCFKTk6WzWYzXCGA8uBwOHTx4kV9//33atGihfz8/EyXBBhDGPFQhw8f1nfffSfp8sO1uGMfUDVwEyygOOaMeCB+WQFVU2k3wXrqqaeUkZHBTbBQbXFkxMNwxz6g6uImWEDJCCMehl9WQNXVoEED7dixQ61bt3ZpP3jwoCIiInTu3DkzhQGGcQdWD8Md+4Cq69FHH9WcOXOKtc+fP1/Dhg0zUBHgGZgz4mGu/LL67xvg8MsKqJz+eGMri8WiBQsWlHoTLKC64jSNhxk7dqzefvtt2e32En9ZXbncV+KOfUBlcMcdd1xVP4vFonXr1lVwNYBnIox4GH5xAQCqG8IIAAAwigmsAADAKMIIAAAwijACAACMIowAAACjCCMAPEJwcLBmzpxpugwABhBGAFRJw4cPV1RUlOkyAFwFwgiAcpOfn2+6BACVEGEEQKn69u2ruLg4xcXFyWazyc/PTy+88IKu3J4oODhYU6ZMUXR0tHx9fTV69GhJ0ieffKIOHTrIarUqODhY06dPd3nd06dP67777pO3t7dCQkL03nvvuWw/evSoLBaL0tLSnG3nzp2TxWJRSkqKs23fvn2699575evrKx8fH/Xu3VtHjhzRiy++qMWLF2vFihWyWCzFxgHwLDybBkCZFi9erJEjR2r79u1KTU3V6NGj1aJFC8XGxkqSXnvtNU2cOFGTJk2SJO3cuVMPPPCAXnzxRT344IPavHmznnjiCTVu3FjDhw+XdPkUyk8//aRvvvlGtWrV0pNPPqnTp0+7VdfJkyd1++23q2/fvlq3bp18fX21adMmFRQU6JlnntH+/fuVnZ2tt956S5LUqFGj8vuhAChXhBEAZbLb7UpKSpLFYlFoaKjS09OVlJTkDCN33nmnnn76aWf/YcOG6a677tILL7wgSWrTpo2+++47TZs2TcOHD9fBgwe1atUqbd++XbfccoskaeHChWrXrp1bdc2ePVs2m01Lly51PrOpTZs2zu3e3t7Ky8tTYGDgdb1/ABWP0zQAytSjRw9ZLBbnemRkpA4dOqTCwkJJUvfu3V3679+/X7169XJp69Wrl3PM/v37VbNmTYWHhzu3t23bVg0aNHCrrrS0NPXu3dvl4ZEAKifCCIDrUq9evXJ/zRo1Lv9q+uOjsy5duuTSx9vbu9z3C8AMwgiAMm3bts1lfevWrWrdurW8vLxK7N+uXTtt2rTJpW3Tpk1q06aNvLy81LZtWxUUFGjnzp3O7QcOHNC5c+ec602aNJEkZWZmOtv+OJlVksLCwvTvf/+7WEi5onbt2s6jNwA8G2EEQJkyMjIUHx+vAwcO6P3339cbb7yhcePGldr/6aef1tq1azVlyhQdPHhQixcv1qxZs/TMM89IkkJDQzVgwAA99thj2rZtm3bu3KlRo0a5HOnw9vZWjx49NHXqVO3fv1/r16/X888/77KfuLg4ZWdn66GHHlJqaqoOHTqkd955RwcOHJB0+Uqfb7/9VgcOHNDPP/9camgBYB5hBECZoqOjdfHiRUVERGjMmDEaN26c8xLeknTr1k0ffvihli5dqo4dO2rixImaPHmy80oaSXrrrbfUtGlT9enTR3/5y180evRo+fv7u7zOokWLVFBQoPDwcI0fP14vv/yyy/bGjRtr3bp1ysnJUZ8+fRQeHq4333zTOYckNjZWoaGh6t69u5o0aVLsaA0Az2Fx/PGkLAD8Qd++fdWlSxdu0w6gQnFkBAAAGEUYAQAARnGaBgAAGMWREQAAYBRhBAAAGEUYAQAARhFGAACAUYQRAABgFGEEAAAYRRgBAABGEUYAAIBR/x+R4DA0g+X4nQAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import pandas as pd\n", + "\n", + "data = pd.DataFrame(data={\n", + " \"product\": [\"pomme\", \"poire\", \"banane\", \"pêche\"],\n", + " \"price\": [1.99, 2.49, 2.99, 3.49],\n", + " \"wpu\": [200, 180, 140, 200]\n", + "})\n", + "display(data)\n", + "display(data.plot.bar(x=\"product\", y=\"price\"))\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d7d1ba1d-2c90-4e35-a44d-f717f26911c6", + "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.12.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/documentation/.gitignore b/documentation/.gitignore new file mode 100644 index 0000000..e4e7469 --- /dev/null +++ b/documentation/.gitignore @@ -0,0 +1 @@ +slides diff --git a/documentation/00-course-plan.md b/documentation/00-course-plan.md new file mode 100644 index 0000000..eee0f3a --- /dev/null +++ b/documentation/00-course-plan.md @@ -0,0 +1,61 @@ +--- +title: Plan de formation Python Traitement de données +author: Steve Kossouho +--- + +# Analyse de données avec Python + +--- + +## 1. Introduction à Pandas pour la manipulation de données + +* La bibliothèque Pandas +* Structures de données pour l'analyse : Dataframe et autres +* Gestion des index +* Gestion des données manquantes +* Fusion de dataframes +* Manipulation des formats de dates +* Mesures statistiques variées sur les DataFrames + +--- + +## 2. Acquisition de données externes + +* Lecture et écriture de fichiers divers +* Gestion de formats structurés : CSV, XML, JSON +* Utilisation de structures/classes Python et bibliothèques de parsing +* Téléchargement de données distantes +* Interrogation de services web REST + +--- + +## 3. Analyse exploratoire des données + +* Métriques d’analyse +* Visualisation des données +* Génération de graphes (Matplotlib) + +--- + +## 4. Analyse financière avec Python + +* Analyse financière : concepts de base +* Panorama des bibliothèques Python : NumPy, SciPy, IPython (Jupyter) +* Calcul matriciel (Numpy) +* Statistiques Descriptives (SciPy) +* Comparaison de populations, mesures d’association (SciPy) + +--- + +## 5. Techniques avancées d'analyse de données + +* Multi-threading et parallélisme → n'utilise pas Spark +* Profiling avec Timeit, cProfile +* Calcul distribué (Celery) + +--- + +## 6. Conclusion et perspectives +* Récapitulatif des compétences acquises +* Applications possibles et cas d'usage +* Ressources pour approfondir l'apprentissage diff --git a/documentation/00-new-course-plan-dawan.md b/documentation/00-new-course-plan-dawan.md new file mode 100644 index 0000000..ec75a9f --- /dev/null +++ b/documentation/00-new-course-plan-dawan.md @@ -0,0 +1,78 @@ +# Analyse de données en Python + +Génère un code HTML pour le contenu Markdown ci-dessous. Les titres de niveau 2 doivent être traduits en balises h4. Les listes à puces doivent être traduites en paragraphes dont les éléments sont simplement séparés par des balises br. Après chaque chapitre, ajoute une balise p avec du texte dans une balise strong, qui propose un thème d'atelier ou un objectif à atteindre à l'issue du chapitre. Le texte peut prendre la forme "Atelier : ..." ou "Objectif : ...". + +## Découvrir l'outil Jupyter + +- Qu'est-ce que Jupyter ? +- Avantages de Jupyter pour l'analyse de données +- Installer Jupyter et lancer un serveur Jupyter + +## Création de données avec Python, Numpy et Pandas + +- Qu'est-ce que NumPy ? +- Création et manipulation de tableaux (1D et 2D) +- Générer des séquences de valeurs +- Introduction à scikit-learn + +## Découverte de Pandas + +- Types de base +- Manipuler et comprendre les séries +- Manipuler et comprendre les dataframes +- Appliquer des calculs et des transformations +- Filtrer des résultats +- Configurer l'affichage de contenu dans Pandas +- Comprendre les index + +## Calculer dans Pandas + +- Appliquer des fonctions d'agrégation (statistiques etc.) +- Conversion des types (Dates, précision des types, etc.) +- Opérations et comparaisons sur les séries et dataframes +- Concepts de base en analyse statistique ou financière + +## Chargement et export de données avec Pandas + +- Protocoles pris en charge (https, file) +- Chargement de fichiers CSV (inférence, dates, en-têtes, encodage) +- Chargement de fichiers Excel (moteurs de chargement : xlrd, openpyxl etc.) +- Prise en charge des archives (zip, gz, bz2, xz) +- Chargement depuis SQLite3 (ou SQLAlchemy) +- Formats de sauvegarde classiques (Excel, CSV) +- Formats SQL (connexion SQLAlchemy ou SQLite3) +- Format Pickle + +## Introduction à l'analyse exploratoire avec Pandas et Matplotlib + +- Présentation de Matplotlib +- Exemples simples (création de diagrammes matplotlib) + - Générer un diagramme en barres + - Générer un diagramme en lignes + - Générer un diagramme en secteurs + - Générer un nuage de points + +## Introduction à l'analyse exploratoire avec Pandas et Plotly + +- Présentation de Plotly +- Créer un simple tracé pour une matrice de corrélation (heatmap) +- Générer des diagrammes en barres et en lignes + +## Générer des tableaux de bord interactifs avec Pandas, Plotly et Dash + +- Présentation de Dash +- Générer un tableau de bord simple avec Dash +- Interactivité dans un tableau de bord Dash + +## Concurrence et parallélisme dans le traitement de données + +- Optimisation des performances via la parallélisation/threading +- Celery (Linux seulement) +- Paralléliser Pandas avec Modin ou Dask + +## Outils et bibliothèques de performance Python + +- Accélération avec Numba, Nuitka, PyPy +- Mesurer le temps d'exécution de votre code +- Découvrir le Profiling + diff --git a/documentation/00-new-course-plan.md b/documentation/00-new-course-plan.md new file mode 100644 index 0000000..68b5227 --- /dev/null +++ b/documentation/00-new-course-plan.md @@ -0,0 +1,225 @@ +# Introduction à Pandas + +## Concepts de base + +- Types de base : `Series` +- Types de base : `DataFrame` +- Types de base : `Index` +- Types alternatifs : `MultiIndex` +- Types alternatifs : `DatetimeIndex` + +## Jupyter + +- Types de cellules, Kernel +- Raccourcis clavier par défaut + +- Qu'est-ce que Jupyter ? +- Avantages de Jupyter pour l'analyse de données +- Installer Jupyter et lancer un serveur Jupyter +- Types de cellules, Kernel +- Raccourcis clavier par défaut + +## Création de données avec Python, Numpy et Pandas + +- Tableaux Numpy +- Création et manipulation de tableaux (1D et 2D) +- Fonctions pour générer des séquences + - numpy.arange() et numpy.linspace() + - pandas.date_range() + - numpy.random.default_rng() + - numpy.empty, ones, zeros, etc. + - Numpy : "Array creation routines" + +## Manipuler une série + +- Quelles sont les propriétés d'une série ? +- Quand obtient-on une série ? +- Types de données (`dtype`) +- Créer une série + - Avec index par défaut + - Avec un index + - Préciser le dtype +- Extraire des informations d'une série (valeurs, index) +- Opérations sur séries (arithmétiques, comparaisons) +- Modifier une valeur d'une série +- Valeurs vides et filtrage (isna, notna, dropna, fillna, count) +- Méthodes des séries booléennes (any, all) +- Récupérer les valeurs distinctes (unique) et retirer des doublons +- Appliquer une fonction aux valeurs d'une série +- Filtrer une série (slice, indexes, conditions, filtres str, filtres dt) +- Concaténation de séries + +## Manipuler un dataframe + +- Quelles sont les propriétés d'un DataFrame ? +- Notions et nomenclature des DataFrame (index, colonnes) +- Quand obtient-on un DataFrame ? +- Types de données (`dtypes`) +- Créer un DataFrame + - Avec des index par défaut (colonnes et lignes) + - Avec des index (colonnes et lignes) +- Extraire des informations d'un DataFrame + - Taille d'un DataFrame + - Index des lignes et colonnes + - Cellule à une position (`at`) + - Une ligne de données (`loc`, `iloc`) + - Une colonne de données (`df[]`) + - Plusieurs lignes de données (`loc`, `iloc`) + - Plusieurs colonnes de données (`df[]`) + - Lignes **et** colonnes (`loc[,]`, `iloc[,]`) + - Filtrage conditionnel (`loc[]`) + - Déduplication des lignes (`drop_duplicates`) + - Tri des lignes + - Récapitulatif +- Valeurs vides et filtrage (isna, notna, dropna, fillna) + - Détecter les valeurs vides (isna, notna) + - Méthodes des séries booléennes (any, all, sum) + - Supprimer les lignes avec des valeurs vides (dropna) + - Remplacer les valeurs vides (fillna) + +- Modifier des informations dans un DataFrame + - Changer toute une colonne ou ligne + - Créer une ligne ou une colonne (+ insertion) + - Changer une cellule + - Retirer une ou plusieurs lignes et colonnes + - Appliquer une fonction +- Comme du SQL, pour des DataFrame + - Ajouter un DataFrame sous ou à côté d'un autre + - Effectuer des jointures (left, right, inner, outer) + - Groupements (group_by) + - Fenêtres +- Configurer l'affichage d'un DataFrame (config ou to_string) + +## Utiliser des index + +- Quelles sont les propriétés d'un index ? +- Extraire des valeurs d'un index (position, slicing) +- Connaître la position d'une valeur dans l'index (`get_loc`) +- Définir un index à un DataFrame ou une série +- Réinitialiser l'index d'un DataFrame ou une série +- Trier par un index (MultiIndex) +- Comprendre les MultiIndex +- Les DatetimeIndex et la fonction date_range + +## Calculer avec des séries et des dataframe + +- Appliquer des fonctions d'agrégation (statistiques etc.) + - Sur une série + - Sur un DataFrame (`describe()`) + - Multiples résultats simultanés (`df.agg()`) + - Fonctions d'agrégation personnalisées +- Conversion des types (to_datetime, `.astype()`, etc.) +- Opérations et comparaisons sur les séries et dataframes + +## Chargement de DataFrame depuis fichiers + +- Protocoles pris en charge (https, file) +- Chargement de fichiers CSV (inférence, dates, en-têtes, encodage) +- Chargement de fichiers Excel (moteurs de chargement : xlrd, openpyxl etc.) +- Chargement depuis document JSON (liste de dictionnaires etc.) +- Prise en charge des archives (zip, gz, bz2, xz) +- Chargement depuis SQLite3 (ou SQLAlchemy) + +## Enregistrement de DataFrame vers des fichiers + +- Formats de sauvegarde classiques (Excel, CSV) +- Formats SQL (connexion SQLAlchemy ou SQLite3) +- Format Pickle + +## Export de DataFrame vers des formats divers + +- Vers un dictionnaire +- Vers une chaîne de caractères +- Vers un document HTML + +## Rendu de graphiques avec Pandas et Matplotlib + +- Matplotlib, qu'est-ce que c'est ? +- Comment ça marche ? + +### Diagrammes avec Pandas + +- Exemples simples (création de diagrammes matplotlib) + - Générer un diagramme en barres + - Générer un diagramme en lignes + - Générer un diagramme en secteurs + - Générer un nuage de points + - Autres diagrammes +- Personnaliser les diagrammes + - Arguments de la méthode `.plot()` (tracé) + +### Matplotlib plus complet + +- Créer un simple tracé et dessiner dedans + - Avec un DataFrame + - Avec des données brutes (Numpy ou Python) + - Dessiner par-dessus un tracé (hold state) +- Objets de type `Figure` et sous-tracés (`Axes`) + - Layout multi-tracés (`subplots`) + - Manipuler un objet de tracé `Axes` +- Afficher les diagrammes (fenêtres) +- Personnaliser les couleurs + - Nommage des couleurs + - Cartes de couleur (`Colormap`) + - Objets colormap fournis par Matplotlib + - Objets colormap personnalisés (classes Colormap) +- Personnaliser les tracés + - Largeurs de traits, contours de zones + - Couleurs de fond + - Couleurs de tracés + - Espacements et mise en page + - Polices de texte + - Génération de légendes + - Libellés (X, Y, titre, Axes, valeurs de barres/secteurs) + - Dessiner par-dessus un tracé (lignes, texte, cercles) + +# Rendu de diagrammes statistiques avec Seaborn + +- Seaborn, c'est quoi ? +- Créer un simple tracé pour une matrice de corrélation (heatmap) + +# Rendu de diagrammes web avec Plotly et Dash + +- Plotly, c'est quoi ? +- Plotly Express, c'est quoi ? +- Exemples simples + - Générer un diagramme en barres + - Générer un diagramme en lignes + - Générer un diagramme en secteurs + - Générer un nuage de points + - Générer un diagramme de Sankey +- Dash, c'est quoi ? +- Générer un tableau de bord simple avec Dash + - Construction avec des composants HTML + - Composant `dash_table.DashTable` + - Composants DCC (Dash Core Components) + - Créer un dashboard via un document HTML (dash-htmlayout) +- Interactivité dans un tableau de bord Dash + - Callbacks + - Entrées + - Sorties + - Propriétés des composants +- Styles CSS et scripts JS dans un document Dash + - Répertoire de recherche des fichiers et configuration +- dash-htmlayout pour créer son layout de tableau de bord sans Python + +# Concurrence et parallélisme dans le traitement de données + +- Optimisation des performances via la parallélisation/threading +- Outils Python d'exécution concurrente/parallèle +- Celery (Linux seulement) + - Configuration + - Installation d'un broker (Redis) + - Envoi d'une tâche Celery + - Démarrage d'une tâche et service Worker + - Tâches périodiques et service Beat +- Spark + pySpark (Linux seulement) + +# Outils et bibliothèques de performance Python + +- Numba +- Nuitka +- PyPy +- Mesurer le temps d'exécution de votre code +- Concept : Profiling + diff --git a/documentation/01-a-pandas-manual.md b/documentation/01-a-pandas-manual.md new file mode 100644 index 0000000..4de91b6 --- /dev/null +++ b/documentation/01-a-pandas-manual.md @@ -0,0 +1,441 @@ +--- +title: Traiter des données avec Pandas +author: Steve Kossouho +--- + +# Analyse de données avec Pandas + +--- + +## La bibliothèque Pandas + +Python est un langage, mais surtout un outil, que vous pouvez augmenter en y adjoignant +le travail réalisé par des développeurs talentueux. Ce travail, c'est des milliers de bibliothèques, +dont les objectifs sont divers, et évidemment, pour le traitement de données, il existe de nombreux outils. + +`pandas`, par exemple, est une bibliothèque destinée surtout à des professionnels ou amateurs qui +souvent, traitent leurs données dans des logiciels payants comme Excel, Stata ou encore SAS. + +![Logo de Pandas](assets/images/logo-pandas.png){width=96px} + +--- + +### Périmètre d'action de Pandas + +Pandas est devenu depuis 2017 la bibliothèque de facto pour traiter des données, généralement tabulaires, +et y effectuer des calculs, tout cela en Python. La bibliothèque est capable de charger en mémoire le +contenu d'un document Excel sous forme d'un objet facile à traiter. + +Il est ainsi possible avec cette bibliothèque, de filtrer des données, les transformer ou encore les +resauvegarder sous un autre format. + +--- + +### Installation de Pandas + +Pandas est disponible sur le dépôt officiel de bibliothèques open-source, [PyPI](https://pypi.org). + +Pour l'installer, il suffit d'ouvrir un terminal et d'y taper la commande suivante : + +```{.bash .numberLines} +# installe la dernière version de pandas si non présent +pip install pandas +``` + +Si vous l'avez déjà installée, vous pouvez passer à la dernière version : + +```{.bash .numberLines} +pip install -U pandas +``` + +--- + +## Types de données pour l'analyse + +La bibliothèque Pandas fournit quelques types de données destinés à contenir et manipuler des données +destinées à l'analyse. Les types en question sont les suivants : + +- `Series` : Données sérielles à 1 dimension +- `DataFrame` : Données tabulaires à 2 dimensions +- `Index` : En-tête d'une ligne d'un `DataFrame` ou `Series` + +Chacun de ces types est documenté sur le [site officiel de Pandas](https://pandas.pydata.org/docs/reference/index.html) + +--- + +### Type `Series` + +Une série, comme son nom l'indique, permet de représenter des données sérielles, ou des séquences +de valeur. Les **séries temporelles**, par exemple, peuvent être manipulées via ce type de données : + +```{.python .numberLines} +import pandas as pd + +# On crée une série en passant les données sous forme de séquence Python ou Numpy +s1 = pd.Series(data=[1, 5, 7, 13, 9, 11, 13]) +print(s1) +``` + +--- + +#### Création manuelle de séries + +Lorsque l'on crée un objet de type `Series`{.python} et qu'on l'affiche, Pandas nous donne +quelques informations intéressantes sur son contenu : + +- Données de la série, précédées d'un _index_ (numéro de ligne) +- Informations sur le type de données de la série + +Dans notre exemple précédent, la série avait comme type `int64`, automatiquement détectée +car toutes les valeurs de la séquence étaient des entiers. +Dans tous les objets de type `Series` ou `DataFrame`, les données, ont toutes un type +géré par la bibliothèque Numpy. En général, le type est récupérable via un attribut nommé +`dtype` ou `dtypes` (data type). + +--- + +#### Types de données dans les objets Pandas + +Les types des données utilisés pour stocker les données dans Pandas sont les suivants : + +- `object` : données de types divers, dont chaînes de caractères +- `int64` : nombres entiers (64 bits maximum) +- `float64` : nombres à virgule flottante (64 bits) +- `bool` : valeurs booléennes +- `datetime64` : représentation d'une date +- `timedelta64` : représentation d'un intervalle de temps +- `category`: choix de valeurs textuelles + +--- + +#### Exemples de séries + +Cet exemple permet de créer une série dont les lignes n'ont pas d'en-tête associé. + +```{.python .numberLines} +import pandas as pd + +# Préciser le type du contenu +# Comme on précise que le `dtype` est `str`, Pandas va automatiquement +# convertir les données de la série en chaînes de caractères +s1 = pd.Series(data=[1, 2, 3, 4, 5, 6], dtype=str) +# Vérifier que les données sont bien interprétées en chaînes +for row in s1: + print(row, type(row)) +``` + +--- + +### Modifier un objet `Series` + +Un objet de type `Series` est immuable, au même titre qu'un `tuple`{.python}. Cela signifie que +l'on ne peut pas directement changer sa taille ou en modifier les éléments. Si l'on souhaite effectuer +une transformation, il faut générer un nouvel objet de type `Series`. + +Par exemple, pour ajouter de nouvelles données à un objet `Series` : + +```{.python .numberLines} +import pandas as pd + +s1 = pd.Series(data=[1, 2, 3, 4]) +s2 = pd.Series(data=[5, 6, 7, 8]) +s3 = pd.concat([s1, s2]) + +print(s3) +``` + +--- + +### Type `DataFrame` + +Un `DataFrame`, comme son nom ne l'indique pas spécialement, est un type de données destiné à recevoir +des données en 2D, tout comme une feuille de calcul Excel. C'est généralement dans des objets de ce type +qu'on charge, justement des feuilles de calcul Excel pour faire des traitements dessus. + +```{.python .numberLines} +import pandas as pd + +df1 = pd.DataFrame(data={"age": [25, 45, 65], "prenom": ["Pierre", "Paul", "Jacques"]}) +print(df1) +``` + +--- + +#### Création manuelle de `DataFrame` + +Pour créer un objet de type `DataFrame`, il y a plusieurs façons de procéder : + +1. Passer une liste de listes +2. Passer une liste de dictionnaires + +--- + +#### `DataFrame` via une liste + +Créer un objet `DataFrame` via une liste de listes fonctionne horizontalement : chaque liste +passée comme élément de la liste principale représente les données d'une ligne du document. + +```{.python .numberLines} +import pandas as pd + +df = pd.DataFrame(data=[["Pierre", 10], ["Jean", 90], ["Marie", 40]]) +print(df) +``` + +Dans ce cas de figure, les colonnes ne portent pas de nom, mais sont identifiables uniquement par +un numéro, ex `0`, `1`, etc. + +--- + +#### Créer un `DataFrame` via une liste en donnant des noms au colonnes + +Pour créer un `DataFrame` via une liste, mais avec la possibilité cette fois-ci d'indiquer +des noms à donner pour identifier les colonnes, il suffit de déclarer notre nouveau `DataFrame` +en précisant un argument nommé `columns`. Cet argument reçoit une liste de valeurs à utiliser comme noms +de colonnes : + +```{.python .numberLines} +import pandas as pd + +df = pd.DataFrame( + data=[["Pierre", 10], ["Jean", 90], ["Marie", 40]], + columns=["nom", "age"] +) +print(df) +``` + +--- + +#### `DataFrame` via un dictionnaire + +Créer un objet `DataFrame` via un dictionnaire permet de représenter le contenu d'un tableau, mais cette +fois-ci, en donnant un nom à nos colonnes : + +```{.python .numberLines} +import pandas as pd + +df = pd.DataFrame(data={"name": ["Mac", "Ann", "Rob"], "age": [33, 44, 55]}) +print(df) +``` + +--- + +### Colonnes d'un `DataFrame` + +Un objet de type `DataFrame` vous propose des outils pour récupérer des informations sur son contenu, +dont ses colonnes d'en-tête. Le bon sens veut que les informations de colonne se trouvent dans un attribut +nommé `.columns`. Il s'agit d'un objet de type `Index` représentant des valeurs identifiant les colonnes du document. + +```{.python .numberLines} +import pandas as pd + +df = pd.DataFrame(data={"name": ["Mac", "Ann", "Rob"], "age": [33, 44, 55]}) +print(df.columns) # Afficher la liste des "noms" de colonne du DataFrame +``` + +--- + +#### Informations des colonnes d'un `DataFrame` + +Notez que dans l'exemple du dessus, nous pouvons récupérer le texte de la ligne d'en-tête +(colonnes), mais il y a d'autres informations que nous pouvons récupérer sur nos colonnes, +par exemple le type de leur contenu. Cette information peut être retrouvée via un attribut +`.dtypes` du `DataFrame` : + +```{.python .numberLines} +import pandas as pd + +df = pd.DataFrame(data={"name": ["Mac", "Ann", "Rob"], "age": [33, 44, 55]}) +print(df.dtypes) # Afficher un objet qui contient les types de toutes les colonnes +print(df.dtypes["age"]) # Afficher le type de la colonne "age" (int64) + +type_list = df.dtypes.to_list() # Récupérer une version en liste : [object, int64] +type_dict = df.dtypes.to_dict() # Récupérer une version en dictionnaire : {"name": object, "age": int64} +print(type_list, type_dict) +``` + +--- + +#### Extraire une colonne d'un DataFrame + +Il est possible d'extraire un `DataFrame` depuis un autre objet `DataFrame`, pour n'en récupérer +que certaines colonnes. Par exemple, on peut extraire une ou plusieurs colonnes avec des syntaxes très similaires : + +```{.python .numberLines} +import pandas as pd + +df = pd.DataFrame(data={"name": ["Mac", "Ann", "Rob"], "age": [33, 44, 55], "nickname": ["Jumbo", "Carrie", "Jet"]}) +print(df["age"]) # Affiche un DataFrame ne contenant que la colonne age +print(df[["age", "nickname"]]) # Affiche un DataFrame avec les colonnes age et nickname + +``` + +--- + +#### Ajouter une colonne à un DataFrame + +Vous pouvez ajouter une nouvelle colonne à un `DataFrame` en utilisant l'objet comme d'habitude : comme un +dictionnaire. Il suffit d'assigner une `Series` ou une suite de valeurs à votre nouvelle clé : + +```python +import pandas as pd + +df = pd.DataFrame(data={"name": ["Mac", "Ann", "Rob"], "age": [33, 44, 55], "nickname": ["Jumbo", "Carrie", "Jet"]}) +df["extra"] = pd.Series([15, 99, 34]) # Ajouter une colonne extra avec 3 valeurs +``` + +TODO : Correspondance entre les index du DF et de la série à expliquer + +--- + +#### Retirer une colonne d'un DataFrame + +Vous pouvez supprimer une colonne à un `DataFrame` en utilisant la méthode `drop()` de l'objet : il existe plusieurs +façons d'utiliser la méthode, mais l'approche la plus simple consiste à utiliser l'argument `columns` : + +```python +import pandas as pd + +df = pd.DataFrame(data={"name": ["Mac", "Ann", "Rob"], "age": [33, 44, 55], "nickname": ["Jumbo", "Carrie", "Jet"]}) + +df.drop(columns=["nickname"], inplace=True) # Directement modifier le DataFrame via l'argument inplace, del df["nickname"] fonctionne aussi +# df.drop(labels=["nickname"], axis=1, inplace=True) # cet appel est équivalent +``` + +--- + +### Type `Index` + +Dans Pandas, un autre type de données, omniprésent mais qu'on ne touche pas souvent directement, +s'appelle un index. Un `Index` est une `Series`, qui contient des valeurs permettant d'identifier soit +une ligne d'un `DataFrame`, soit une colonne. + +Globalement, dans un `DataFrame`, on utilise un index accéder au contenu d'une ligne d'un `DataFrame` ou +un enregistrement dans une `Series`. + +--- + +#### Exemple d'utilisation d'un `Index` + +Si vous avez, par exemple, un `DataFrame` avec quelques lignes, vous pouvez assigner des index à chacune +de vos lignes pour référencer ces dernières : + +```{.python .numberLines} +import pandas as pd + +# Créer un DataFrame avec deux colonnes et 3 lignes +df = pd.DataFrame( + data={"name": ["Mac", "Ann", "Rob"], "age": [33, 44, 55]}, + index=["u1", "u2", "u3"] # Associer des noms aux lignes du DataFrame via un index +) +print(df) + +# Extraire du DataFrame les lignes 0 et 1 (en utilisant le numéro de ligne) avec iloc +df2 = df.iloc[[0, 1]] # ou df2 = df.iloc[0:2] +print(df2) + +# Extraire du DataFrame les lignes "u1" et "u2" (en utilisant le nom de ligne) avec loc +df3 = df.loc[["u1", "u2"]] # ou df4 = df.loc["u1"] pour extraire une seule ligne +print(df3) +``` + +--- + +#### Associer un `Index` à un `DataFrame` + +Dans la pratique, l'indexation dans un `DataFrame` est assez flexible. On peut utiliser les labels existants pour créer un nouvel `Index` ou modifier directement l'`Index` d'un `DataFrame`. Cela offre une manière puissante de manipuler et de traiter les données. + +Par exemple, si vous avez une colonne qui a des valeurs uniques et que vous voulez l'utiliser comme un `Index`, vous pouvez le faire avec la méthode `set_index()` : + +```{.python .numberLines} +import pandas as pd + +# Supposons que nous ayons un DataFrame avec une colonne "ID" unique +df = pd.DataFrame( + data={"ID": [101, 102, 103], "name": ["Mac", "Ann", "Rob"], "age": [33, 44, 55]} +) + +# Utiliser la colonne "ID" comme index +df.set_index("ID", inplace=True) + +print(df) +``` + +Dans cet exemple, la colonne "ID" a été définie comme `Index` du `DataFrame`. Notez l'utilisation du paramètre `inplace=True` pour modifier le `DataFrame` existant, au lieu de créer un nouveau `DataFrame`. + +--- + +Une fois que l'`Index` est assigné, on peut l'utiliser pour accéder aux lignes du `DataFrame`. Par exemple : + +```{.python .numberLines} +# Obtenir la ligne correspondant à l'ID 102 +print(df.loc[102]) +``` + +En résumé, l'`Index` est un outil puissant dans Pandas qui facilite la manipulation, l'organisation et l'accès aux données. Il est particulièrement utile lorsqu'on travaille avec de grands ensembles de données où l'efficacité est cruciale. + +--- + +## Autres exemples de création de `DataFrame` + +--- + +### Création d'un `DataFrame` à partir d'une liste de dictionnaires + +```{.python .numberLines} +import pandas as pd + +data = [ + {"name": "Tom", "age": 25, "job": "Developer"}, + {"name": "Nick", "age": 35, "job": "Teacher"}, + {"name": "Juli", "age": 30, "job": "Doctor"}, +] + +df = pd.DataFrame(data) +print(df) +``` + +Dans cet exemple, chaque élément de la liste représente une ligne de données et chaque dictionnaire représente une colonne. + +--- + +### Création d'un `DataFrame` à partir d'un dictionnaire de listes + +```{.python .numberLines} +import pandas as pd + +data = { + "name": ["Tom", "Nick", "Juli"], + "age": [25, 35, 30], + "job": ["Developer", "Teacher", "Doctor"], +} + +df = pd.DataFrame(data) +print(df) +``` + +Dans ce cas, chaque clé du dictionnaire représente une colonne et chaque liste représente les lignes de cette colonne. + +--- + +### Création d'un `DataFrame` à partir d'une liste de listes + +```{.python .numberLines} +import pandas as pd + +data = [ + ["Tom", 25, "Developer"], + ["Nick", 35, "Teacher"], + ["Juli", 30, "Doctor"], +] + +df = pd.DataFrame(data, columns=["name", "age", "job"]) +print(df) +``` + +Dans cet exemple, chaque élément de la liste principale est une liste qui représente une ligne de données. + +--- + +Chacune de ces méthodes a ses avantages, selon la structure des données avec lesquelles vous travaillez. +La clé est de comprendre la structure de vos données et de choisir la méthode qui convient le mieux à votre situation. diff --git a/documentation/01-c-pandas-actions.md b/documentation/01-c-pandas-actions.md new file mode 100644 index 0000000..e5d2aa8 --- /dev/null +++ b/documentation/01-c-pandas-actions.md @@ -0,0 +1,817 @@ +--- +title: Manipuler des données avec Pandas +author: Steve Kossouho +--- + +# Analyse de données fichier avec Pandas + +--- + +## Les `Index` dans une `Series` + +Un objet de type `Series` est un jeu de données à une dimension. Ce qui n'empêche pas d'associer +un index à chacune des lignes du document. Un des intérêts est de pouvoir associer une étiquette +à chacune des données, par exemple un moment dans le temps. Cela permet d'obtenir une série temporelle +en bonne et dûe forme. + +```{.python .numberLines} +import pandas as pd + +s0 = pd.Series(data=[2, 5, 2, 6], index=["ligne1", "ligne2", "ligne3", "ligne4"]) +s1 = pd.Series(data=[2, 5, 2, 6], index=pd.date_range("2023-01-01", "2023-01-31", 4)) +s2 = pd.Series(data=[2, 5, 2, 6], index=["2022-01-01", "2022-01-02", "2022-01-03", "2022-01-04"]) +s3 = pd.Series(data=[2, 5, 2, 6]) # Créer une série sans index +s3 = s3.set_axis(["ligne1", "ligne2", "ligne3", "ligne4"]) # Utilise ces valeurs comme index. Renvoie une nouvelle série. +print(s1, s2) +print(s0, s3) +``` + +--- + +### Petit aparté : utilisation de `date_range()` + +La fonction de Pandas nommée `date_range()` permet de générer une suite de dates, qu'on pourrait +utiliser pour générer des colonnes d'un `DataFrame` ou des index à associer aux lignes d'un `DataFrame` ou une `Series`. + +La fonction permet de générer des dates à intervalles réguliers, mais peut faire beaucoup plus intéressant : + +```{.python .numberLines} +import pandas as pd + +# Génère une liste de 8 dates, contenant la date de début et de fin, ainsi que 6 autres dates +# uniformément dispersés (avec les heures, minutes, secondes et **nanosecondes**) +print(pd.date_range("2023-01-01", "2023-01-31", periods=8) +``` + +Un problème potentiel de cette méthode, c'est justement qu'on n'obtient pas toujours des dates exactes à minuit. +Cela peut s'arranger, en utilisant la fonction avec un autre argument. + +TODO: Reformuler ce truc + +--- + +Si on utilise la même fonction, mais en utilisant l'argument `freq` au lieu de l'argument `periods` (on ne peut +pas utiliser les deux en même temps), on peut demander à Pandas de générer une suite de dates, répondant à certains critères, documentés +[ici](https://pandas.pydata.org/docs/user_guide/timeseries.html#offset-aliases) + +```{.python .numberLines} +import pandas as pd + +# Génère une liste de dates, contenant la date de début et de fin, une par jour du calendrier +print(pd.date_range("2023-01-01", "2023-01-31", freq="D")) # D signifie chaque jour de la semaine +print(pd.date_range("2023-01-01", "2023-01-31", freq="W-SUN")) # W-SUN signifie chaque dimanche +print(pd.date_range("2023-01-01", "2023-01-31", freq="W-MON")) # W-MON signifie chaque lundi +print(pd.date_range("2023-01-01", "2023-01-31", freq="W-TUE")) # W-MON signifie chaque mardi +print(pd.date_range("2023-01-01", "2023-01-02", freq="H")) # H signifie chaque heure +``` + + + +--- + +## Les données absentes (`Not a Number`) + +Dans un objet `Series` ou un objet `DataFrame`, toutes les cellules n'ont pas forcément de données. + +Prenons un exemple au hasard : une table contenant des informations d'identité, et qui contient des colonnes telles que `nom`, `prenom`, et `nom de naissance`, peut présenter des lignes de contenu où la colonne `nom de naissance` ne sera pas renseignée. + +Lorsque c'est le cas, Pandas représente le contenu de la cellule comme étant `NaN` (not a number), une donnée qui en Python standard serait la valeur `None`{.python}. + +Dans de nombreux calculs fournis par Pandas, il est souvent facile d'exclure les cellules `NaN` des +résultats, voire de retirer des informations selon l'état de remplissage de cellules. + +--- + +## Gestion des données manquantes (NaN) + +Pandas offre plusieurs méthodes pour gérer les données manquantes, identifiées par la valeur `NaN` (Not a Number) : + +- `isna()` ou `isnull()` : ces deux méthodes retournent un masque booléen indiquant les entrées manquantes. +- `notna()` ou `notnull()` : ces méthodes fonctionnent comme les précédentes, mais renvoient `True` pour les entrées non manquantes. +- `dropna()` : cette méthode permet de supprimer les lignes (ou colonnes, selon l'axe choisi) contenant des entrées manquantes. +- `fillna()` : cette méthode permet de remplir les entrées manquantes avec une valeur spécifiée. + +--- + +Exemple d'utilisation : + +```{.python .numberLines} +import pandas as pd +import numpy as np + +# Création d'un DataFrame avec des valeurs manquantes +df = pd.DataFrame({ + "A": [1, 2, np.nan], + "B": [5, np.nan, np.nan], + "C": [1, 2, 3] +}) +print(df) +``` + +--- + +```{.python .numberLines} +# Utilisation de dropna() pour supprimer les lignes avec des valeurs NaN +df_drop = df.dropna() +# On peut aussi choisir quelles colonnes considérer pour la vérification des valeurs NaN +# Supprimer les lignes entières où on a NaN dans la colonne "A" uniquement +df_drop2 = df.dropna(subset=["A"]) +print(df_drop) +print(df_drop2) +``` + +--- + +Pour remplacer des valeurs `NaN` dans un `DataFrame`, on a plusieurs possibilités : + +```{.python .numberLines} +# Utilisation de fillna() pour remplir les valeurs manquantes +# Ici, on remplace TOUTES les valeurs NaN du DataFrame par une valeur choisie +df_fill = df.fillna(value='FILL VALUE') + +# Si on souhaite choisir la valeur par défaut par colonne +# Ici, on indique qu'il faut remplacer les valeurs NaN de la colonne "A" +# par 0, et les valeurs NaN de la colonne "B" par -1 +df_fill2 = df.fillna(value={"A": 0, "B": -1}) + +print(df_fill) +print(df_fill2) +``` + +--- + +Pour avoir une carte des valeurs `NaN` ou équivalentes dans un `DataFrame`, on a les méthodes `isna()` : + +```{.python .numberLines} +# Utilisation de isna() pour récupérer un DataFrame de valeurs booléennes. +# On aura un `True` pour les valeurs `None`, `NaN`, `NaT` etc. +df_empty_map = df.isna() + +print(df_empty_map) +``` + +--- + +## Gestion des index + +Nous avons déjà vu comment utiliser des index avec un `DataFrame`. Il existe plusieurs méthodes utiles pour gérer ces index : + +- `reset_index()` : Permet de réinitialiser l'index à un index par défaut (0, 1, 2, ...). +- `set_index()` : Utiliser une colonne différente pour l'index. + +Exemple d'utilisation : + +```{.python .numberLines} +import pandas as pd + +# Création d'un DataFrame avec un index +df = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]}, index=["x", "y", "z"]) +print(df) +``` + +--- + +```{.python .numberLines} +# Réinitialisation de l'index +df = ... +# df.reset_index() renvoie un nouveau DataFrame avec l'index mis à jour. +df_reset = df.reset_index() +# df.reset_index(inplace=True) modifie le DataFrame en modifiant son index. +df.reset_index(inplace=True) +print(df_reset) + +# Utilisation d'une autre colonne pour l'index +# Les valeurs de la colonne "A" seront utilisées comme index pour référencer les lignes +df_set = df.set_index("A") +print(df_set) +``` + +--- + +## Filtrage et modifications d'un dataframe + +Pandas offre une multitude de méthodes pour filtrer et modifier un `DataFrame`. Par exemple, il est possible de sélectionner des colonnes ou des lignes spécifiques, de filtrer selon certaines conditions, ou encore de modifier des valeurs. + +--- + +Exemple d'utilisation : + +```{.python .numberLines} +import pandas as pd + +# Création d'un DataFrame +df = pd.DataFrame({"A": [1, 2, 3, 4], "B": [5, 6, 7, 8], "C": ["a", "b", "c", "d"]}) +# Sélection d'une colonne +print(df["A"]) +# Sélection de plusieurs colonnes +print(df[["A", "B"]]) +# Filtrage selon une condition +# On peut filtrer avec des conditions sans passer par df.iloc +# Mais ce sera plus explicite d'utiliser df.iloc +print(df.loc[df["A"] > 2]) # Récupérer les lignes où A > 2 +print(df.loc[(df["A"] > 2) & (df["B"] == 8)]) # Récupérer les lignes où A > 2 et B = 8 +print(df.loc[~(df["A"] > 2) & (df["B"] == 8)]) # Récupérer les lignes où A n'est pas > 2 et B = 8 + +# Modification de valeurs : +# Changer les lignes de df où la colonne "A" contient une +# valeur supérieure à 2, et y mettre la valeur 10 dans les colonnes B et C +df.loc[df["A"] > 2, ["B", "C"]] = 10 +# Si je veux mettre deux valeurs différentes dans B et C lorsque la condition +# est vérifiée, j'écrirai plutôt +df.loc[df["A"] > 2, ["B", "C"]] = (9, 12) +print(df) +``` + +--- + +### Tri des lignes d'un `DataFrame` + +Il est fréquent de vouloir réorganiser les données d'un `DataFrame` via un tri sur une ou plusieurs colonnes. +Le type propose naturellement une méthode nommée `.sort_values()` + +--- + +## Slicing et lignes de `DataFrame` + +Nous avons vu qu'il est possible de filtrer un `DataFrame` via des critères avancés. De façon plus générale, le filtrage de lignes peut se faire avec deux attributs : + +- `df.iloc` : extraire des lignes individuelles par leur numéro séquentiel dans le dataframe; +- `df.loc` : extraire des lignes individuelles par la valeur assignée comme index aux lignes du document. + +Les deux méthodes s'utilisent généralement de la même manière, en faisant attention à utiliser des alias d'index dans le +cas de `df.loc`. + +--- + +### Extraire une ligne d'un `DataFrame` + +Au même titre que dans une liste Python, il est très simple d'extraire une ligne d'un objet `DataFrame` : + +```python +import pandas as pd + +df = pd.DataFrame({"A": [1, 2, 3, 4], "B": [5, 6, 7, 8], "C": ["a", "b", "c", "d"]}, index=["l1", "l2", "l3", "l4"]) +row_a = df.loc["l1"] # extraire la première ligne +row_b = df.iloc[0] # extraire la première ligne + +print(row_a, row_b) +``` + +--- + +### Extraire plusieurs lignes distinctes d'un `DataFrame` + +Impossible avec des types standard en Python, il est très simple avec Pandas d'extraire des lignes distinctes d'un objet `DataFrame` : + +```python +import pandas as pd + +df = pd.DataFrame({"A": [1, 2, 3, 4], "B": [5, 6, 7, 8], "C": ["a", "b", "c", "d"]}, index=["l1", "l2", "l3", "l4"]) +rows_a = df.iloc[[0, 2]] # extraire les 1e et 3e lignes + +print(rows_a) +``` + +--- + +### Filtrer les lignes d'un `DataFrame` avec des booléens + +On peut filtrer les lignes d'un objet `DataFrame` en passant une liste de booléens. Pour chaque booléen valant `True`, la ligne +correspondante sera conservée dans la sortie : + +```python +import pandas as pd + +df = pd.DataFrame({"A": [1, 2, 3, 4], "B": [5, 6, 7, 8], "C": ["a", "b", "c", "d"]}, index=["l1", "l2", "l3", "l4"]) +rows_a = df.iloc[[False, True, True, False]] # extraire les 2e et 3e lignes + +print(rows_a) +``` + +Cette possibilité de filtrer via une liste de booléens, associée au fonctionnement des opérateurs sur les `Series`, +permet des choses très intéressantes pour facilement filtrer des `DataFrame` + +--- + +#### Filtrer avec des conditions + +Utiliser des opérateurs de comparaison sur une colonne de données renverra toujours une série de même longueur, contenant +des booléens indiquant si la comparaison était vraie pour chaque valeur. + +```python +import pandas as pd + +df = pd.DataFrame({ + "A": [1, -2, 3, -4], + "B": [5, 6, 7, 8], + "C": ["ant", "ball", "chord", "double"] +}, index=["l1", "l2", "l3", "l4"]) +# Masque de valeurs positives +positive_a = df["A"] >= 0 +# L'opposé d'une condition utilise l'opérateur +# de complément binaire, le ~ +exclude_cond = ~df["A"] == 0 +# Masque de valeurs dans une plage +range_a = df["A"].between(0, 100, inclusive="both") +``` + +--- + +Les séries de booléens résultantes peuvent naturellement assemblées entre elles via des opérations logiques, booléennes : + +```python +import pandas as pd + +df = pd.DataFrame({"A": [1, -2, 3, -4], "B": [5, 6, 7, 8], "C": ["ant", "ball", "chord", "double"]}, index=["l1", "l2", "l3", "l4"]) +multi_condition = (df["A"] >= 0) & (df["B"] > 6) # lignes où A est positif et B est > 6 + +print(multi_condition) +``` + +--- + +#### Conditions sur des chaînes de caractères + +Il est facile d'utiliser des opérateurs classiques pour tester des conditions sur des +colonnes d'un objet `DataFrame`. Par exemple : + +```python +filtered_df = df[df["column"] > 1] +``` + +Si l'on souhaite filtrer sur une colonne textuelle, on peut être intéressé +de pouvoir considérer une partie d'une chaîne, ou bien ignorer la casse du texte. +Pour cela nous avons sur les objets `Series` un attribut `.str` : + +```python +filtered_df = df[df["column"].str.contains("(?i)texte")] +``` + +La méthode comprend les expressions régulières pour plus de contrôle sur les valeurs à tester. + +--- + +#### Conditions avec des fonctions + +Il peut arriver qu'une condition de test à appliquer à une colonne nécessite autre chose que simplement le contenu +brut de la colonne; par exemple, le nombre de caractères d'une colonne textuelle pourrait être utilisé pour +discriminer les résultats. Dans ce cas de figure, on utilisera plutôt la méthode `apply` de la série : + +```python +import pandas as pd + +df = pd.DataFrame( + {"A": [1, -2, 3, -4], "B": [5, 6, 7, 8], "C": ["ant", "ball", "chord", "double"]}, + index=["l1", "l2", "l3", "l4"] +) +# Dire si une ligne de C a plus de 4 caractères +# La fonction renvoie une série de valeurs booléennes +long_c = df["C"].apply(lambda val: len(val) > 4) + +print(long_c) +print(df.loc[long_c]) +``` + +--- + +### Slicing sur les lignes + +La syntaxe normale du slicing fonctionne sur les lignes d'un `DataFrame`, ce qui vous permet de récupérer simplement +une partition des lignes du `DataFrame` original. + +```python +import pandas as pd + +df = pd.DataFrame( + {"A": [1, -2, 3, -4], "B": [5, 6, 7, 8], "C": ["ant", "ball", "chord", "double"]}, + index=["l1", "l2", "l3", "l4"] +) +partition = df.iloc[0:2] + +print(partition) +``` + +--- + +### Plus : Multiple slicing sur les lignes + +La syntaxe normale du slicing fonctionne sur les lignes d'un `DataFrame`, ce qui vous permet de récupérer simplement +une partition des lignes du `DataFrame` original. Un outil de `numpy` nous permet d'appliquer un slicing multiple sur les +lignes d'un `DataFrame` : + +```python +import pandas as pd +import numpy as np + +df = pd.DataFrame( + {"A": [1, -2, 3, -4], "B": [5, 6, 7, 8], "C": ["ant", "ball", "chord", "double"]}, + index=["l1", "l2", "l3", "l4"] +) +partition = df.iloc[np.r_[0:1, 2:4]] # extraire les lignes 0 et 2 à 3 avec du slice + +print(partition) +``` + +--- + +## Grouper les contenus d'un DataFrame pour calculs + +Pandas propose des méthodes pour grouper les données selon certains critères et effectuer des calculs sur ces groupes. +C'est ce qu'on appelle souvent l'opération de `group by` (groupement). +Cela est particulièrement utile pour l'analyse de données. + +Exemple d'utilisation : + +```{.python .numberLines} +import pandas as pd +import numpy as np + +# Création d'un DataFrame +df = pd.DataFrame({ + "A": ["foo", "bar", "foo", "bar", "foo", "bar", "foo", "foo"], + "B": ["one", "one", "two", "three", "two", "two", "one", "three"], + "C": np.random.randn(8), + "D": np.random.randn(8) +}) + +# Grouper par colonne "A" et calculer la somme sur chaque groupe +grouped = df.groupby("A").sum() +print(grouped) +``` + +--- + +## Fusion de dataframes + +Pandas propose plusieurs méthodes pour combiner des `DataFrame`, comme `concat()`, `merge()`, ou `join()`. Ces opérations sont similaires à celles que l'on peut réaliser avec des bases de données SQL. + +Exemple d'utilisation : + +```{.python .numberLines} +import pandas as pd + +# Création de deux DataFrame +df1 = pd.DataFrame({ + "A": ["A0", "A1", "A2", "A3"], + "B": ["B0", "B1", "B2", "B3"], + "C": ["C0", "C1", "C2", "C3"], + "D": ["D0", "D1", "D2", "D3"] +}) + +df2 = pd.DataFrame({ + "A": ["A4", "A5", "A6", "A7"], + "B": ["B4", "B5", "B6", "B7"], + "C": ["C4", "C5", "C6", "C7"], + "D": ["D4", "D5", "D6", "D7"] +}) + +# Concaténation des deux DataFrame +result = pd.concat([df1, df2]) +print(result) +``` + +--- + +## Manipulation des formats de dates + +Pandas offre une multitude de méthodes pour manipuler les dates. +Vous pouvez convertir des chaînes de caractères en dates, extraire des composants spécifiques d'une date (comme l'année, le mois, le jour, etc.), ou encore effectuer des calculs avec des dates. + +Exemple d'utilisation : + +```{.python .numberLines} +import pandas as pd + +# Création d'un DataFrame avec une colonne date +df = pd.DataFrame({ + "date": ["2023-01-01", "2023-01-02", "2023-01-03"], + "value": [1, 2, 3] +}) + +# Afficher les types des colonnes pour s'assurer que la colonne de date a le bon type +print(df.dtypes) # Regardez le résultat pour être sûr + +# Si le type est incorrect, faire la conversion de la colonne vers le type datetime +df["date"] = pd.to_datetime(df["date"]) + +# Extraction de l'année, du mois, et du jour dans 3 nouvelles colonnes +df["year"] = df["date"].dt.year +df["month"] = df["date"].dt.month +df["day"] = df["date"].dt.day +print(df) +``` + +--- + +## Mesures statistiques variées sur les DataFrames + +Pandas propose plusieurs méthodes pour calculer des statistiques sur un `DataFrame`, comme la moyenne (`mean()`), la somme (`sum()`), l'écart-type (`std()`), le minimum (`min()`), le maximum (`max()`), et bien d'autres. + +Exemple d'utilisation : + +```{.python .numberLines} +import pandas as pd + +# Création d'un DataFrame +df = pd.DataFrame({ + "A": [1, 2, 3, 4, 5], + "B": [6, 7, 8, 9, 10] +}) + +# Calcul de la moyenne, de la somme, et de l'écart-type sur chaque colonne +mean = df.mean() # Ceci est une `Series` avec une valeur calculée par colonne +sum = df.sum() # Ceci aussi est une `Series` +std = df.std() # Pareil + +print(mean) +print(sum) +print(std) +``` + +--- + +### Comment ajouter une `Series` comme nouvelle ligne d'un `DataFrame` + +Cela peut être intéressant d'ajouter à un `DataFrame` une ligne qui provient d'un objet `Series` (par exemple, les résultats de moyennes, sommes ou écarts-types etc.) Pour cela, c'est assez simple, il faut se servir de l'attribut `.loc` d'un `DataFrame` : + +```{.python .numberLines} +import pandas as pd + +# Création d'un DataFrame +df = pd.DataFrame({"A": [1, 2, 3, 4, 5], "B": [6, 7, 8, 9, 10]}) +# Calcul de la moyenne, de la somme, et de l'écart-type sur chaque colonne +mean = df.mean() # Ceci est une `Series` avec une valeur calculée par colonne + +# Ajouter la `Series` dans `mean` comme nouvelle ligne dans `df` dont l'index est "moyenne" +df.loc["moyenne"] = mean +df.loc["hasard"] = [99, 25] # On peut même définir des lignes avec des valeurs dans une liste +print(df) # Une nouvelle ligne "moyenne" devrait être présente dans le DataFrame +``` + +--- + +### Noms des méthodes les plus fréquemment utilisées + +Pour faire des calculs statistiques sur les colonnes d'un `DataFrame`, les méthodes disponibles de +base dans Pandas ont des noms pas toujours faciles à deviner : + +- `df.mean()` : Calcule la moyenne des colonnes +- `df.sum()` : Calcule la somme des colonnes +- `df.std()` : Calcule l'écart-type (racine de la variance) +- `df.var()` : Calcule la variance +- `df.median()` : Valeur centrale des éléments d'une série +- `df.quantile(q)` : Calcule un quantile (q entre 0.0 et 1.0) +- `df.mode()` : Renvoie un DataFrame des valeurs modales de chaque colonne +- `df.max()` et `df.min()` : Calculer les extrêmes +- `df.cum*()` : Fonctions cumulatives qui renvoient des DataFrame + +--- + +#### Mode d'un `DataFrame` + +Le mode d'un `DataFrame` renvoie un `DataFrame` avec toutes les colonnes, et autant de lignes qu'il y a de valeurs modales par colonne. (_Une valeur modale est la valeur qui revient le plus souvent dans une série_) + +Si j'ai un `DataFrame` avec une colonne `A` et une colonne `B`, et que `A` possède une seule valeur modale qui est `10`, et que `B` possède 3 valeurs modales qui sont `"pomme"`, `"citron"`, et `"ananas"`, le résultat du mode du `DataFrame` ressemblera à ça : + +``` + A B + 10 pomme + NaN citron + NaN ananas +``` + +Les colonnes où il y a moins de valeurs modales sont remplies avec des `NaN`. + +--- + +## Faire des liaisons entre plusieurs `DataFrame` + +Des fois, on est obligé de récupérer des informations différentes depuis plusieurs `DataFrame` qui ont des données en commun. Par exemple : + +- Une table d'assurés sociaux : `numero_secu`, `prenom`, `nom`, `naissance`... +- Une table de remboursements médicaments : `num_secu`, `montant`, `date`, `categorie`... + +Si on veut travailler sur le `DataFrame` des remboursements, en y ajoutant les informations de prénom, nom et date de naissance pour chaque remboursement, on peut faire une fusion avec Pandas. + +```{.python .numberLines} +import pandas as pd +# Imaginez que ces deux fichiers factices correspondent à nos deux Dataframes +df_assures = pd.read_csv("assures.csv") +df_remboursements = pd.read_csv("remboursements.csv") + +# On veut partir du DataFrame des remboursements pour y ajouter +# des colonnes prises depuis la liste des assurés. +resultat = df_remboursements.merge(df_assures, how="left", on_left="num_secu", on_right="numero_secu") +print(resultat) +``` + +--- + +Le résultat qui est affiché contient toutes les colonnes des assurés et des remboursements, et contient autant de lignes que dans la table de "gauche", à savoir `df_remboursements`. + +Les colonnes sont `numero_secu`, `prenom`, `nom`, `naissance`, `num_secu`, `montant`, `date`, `categorie`. +Les colonnes `num_secu` et `numero_secu` auront les memes valeurs, car c'est justement sur ces colonnes qu'on a fait la fusion. Attention cependant ! + +--- + +### Stratégie de fusion de `DataFrame` + +La méthode `.merge()` accepte un argument `how=` qui permet de préciser comment le résultat de la fusion doit etre généré. Les valeurs possibles de cet argument sont : `"left"`, `"right"`, `"inner"`, `"outer"`. + +Si je possède les `DataFrame` suivants : + +--- + +**Personnes** + +``` + id prenom nom naissance +0 1 Jacques Dupont 1959 +1 2 Paul Biya 1947 +2 3 Marie Curie 1861 +3 5 Jules César 0 +``` + +--- + +**Transactions** + +``` + id_transaction id_client montant +0 1 1 100 +1 2 1 500 +2 3 2 -90 +3 4 2 200 +4 5 1 30 +5 6 3 15 +6 7 2 -60 +7 8 1 -10 +8 9 1 250 +9 10 2 170 +10 11 3 90 +15 16 4 77 +``` + +--- + +#### Stratégie `left` + +Si j'utilise la stratégie `"left"`, alors toutes les lignes du `DataFrame` de gauche (`df_remboursements`) seront conservées, et on y ajoutera les colonnes du `DataFrame` de droite, en y choisissant comme données celles de la ligne +où la colonne `"numero_secu"` a la meme valeur que dans la colonne `"num_secu"`. + +Si il n'existe pas de ligne dans le `DataFrame` de droite où la colonne `"numero_secu"` a une valeur qui correspond, les +colonnes ajoutées contiendront `NaN`. En gros : on conservera tous les enregistrements de **gauche**, en ajoutant des `NaN` à droite s'il n'y a aucune correspondance trouvée. + +--- + +Résultat : + +``` + id_transaction id_client montant id prenom nom naissance +0 1 1 100 1.0 Jacques Dupont 1959.0 +1 2 1 500 1.0 Jacques Dupont 1959.0 +2 3 2 -90 2.0 Paul Biya 1947.0 +3 4 2 200 2.0 Paul Biya 1947.0 +4 5 1 30 1.0 Jacques Dupont 1959.0 +5 6 3 15 3.0 Marie Curie 1861.0 +6 7 2 -60 2.0 Paul Biya 1947.0 +7 8 1 -10 1.0 Jacques Dupont 1959.0 +8 9 1 250 1.0 Jacques Dupont 1959.0 +9 10 2 170 2.0 Paul Biya 1947.0 +10 16 4 77 NaN NaN NaN NaN +``` + +--- + +#### Stratégie `right` + +Si j'utilise la stratégie `"right"`, alors toutes les lignes du `DataFrame` de droite (`df_assures`) +seront conservées, et on y ajoutera les colonnes du `DataFrame` de gauche, en y choisissant comme données celles de la ligne +où la colonne `"num_secu"` a la meme valeur que dans la colonne `"num_secu"`. Si plusieurs lignes correspondent, on ajoutera autant de lignes que nécessaire. + +Si il n'existe pas de ligne dans le `DataFrame` de gauche où la colonne `"num_secu"` a une valeur qui correspond, les colonnes ajoutées contiendront `NaN`. + +--- + +Résultat : + +``` + id_transaction id_client montant id prenom nom naissance +0 1.0 1.0 100.0 1 Jacques Dupont 1959 +1 2.0 1.0 500.0 1 Jacques Dupont 1959 +2 5.0 1.0 30.0 1 Jacques Dupont 1959 +3 8.0 1.0 -10.0 1 Jacques Dupont 1959 +4 9.0 1.0 250.0 1 Jacques Dupont 1959 +5 3.0 2.0 -90.0 2 Paul Biya 1947 +6 4.0 2.0 200.0 2 Paul Biya 1947 +7 7.0 2.0 -60.0 2 Paul Biya 1947 +8 10.0 2.0 170.0 2 Paul Biya 1947 +9 6.0 3.0 15.0 3 Marie Curie 1861 +10 NaN NaN NaN 5 Jules César 0 +``` + +--- + +#### Stratégie `inner` + +Avec la stratégie `inner` on ne garde que les lignes où les colonnes utilisées pour faire la fusion ont une correspondance +de l'autre coté de de la fusion. En gros, on n'ajoute jamais dans le résultat de ligne ou on aurait des colonnes avec `NaN`. + +--- + +Résultat : + +``` + id_transaction id_client montant id prenom nom naissance +0 1 1 100 1 Jacques Dupont 1959 +1 2 1 500 1 Jacques Dupont 1959 +2 5 1 30 1 Jacques Dupont 1959 +3 8 1 -10 1 Jacques Dupont 1959 +4 9 1 250 1 Jacques Dupont 1959 +5 3 2 -90 2 Paul Biya 1947 +6 4 2 200 2 Paul Biya 1947 +7 7 2 -60 2 Paul Biya 1947 +8 10 2 170 2 Paul Biya 1947 +9 6 3 15 3 Marie Curie 1861 +``` + +--- + +#### Stratégie `outer` + +Avec la stratégie `outer` on garde toutes lignes et si les colonnes utilisées pour faire la fusion n'ont pas de correspondance +de l'autre coté de la fusion, on ajoute simplement des colonnes avec `NaN`.. + +--- + +Résultat : + +``` + id_transaction id_client montant id prenom nom naissance +0 1.0 1.0 100.0 1.0 Jacques Dupont 1959.0 +1 2.0 1.0 500.0 1.0 Jacques Dupont 1959.0 +2 5.0 1.0 30.0 1.0 Jacques Dupont 1959.0 +3 8.0 1.0 -10.0 1.0 Jacques Dupont 1959.0 +4 9.0 1.0 250.0 1.0 Jacques Dupont 1959.0 +5 3.0 2.0 -90.0 2.0 Paul Biya 1947.0 +6 4.0 2.0 200.0 2.0 Paul Biya 1947.0 +7 7.0 2.0 -60.0 2.0 Paul Biya 1947.0 +8 10.0 2.0 170.0 2.0 Paul Biya 1947.0 +9 6.0 3.0 15.0 3.0 Marie Curie 1861.0 +10 16.0 4.0 77.0 NaN NaN NaN NaN +11 NaN NaN NaN 5.0 Jules César 0.0 +``` + +--- + +### Appliquer un Pivot + +La méthode `pivot` des `DataFrame` permet de récupérer une représentation d'un `DataFrame`, réorganisé autour de certaines colonnes/index. + +Par exemple, si nous avons les données suivantes : + +```{.python .numberLines} +import pandas as pd + +df = pd.DataFrame({ + 'country': ['fra', 'fra', 'fra', 'fra', 'bel', 'bel', 'bel', "bel"], + 'district': ['north', 'east', 'west', 'south', 'north', 'east', 'west', 'south'], + 'population': [10_000, 30_000, 50_000, 70_000, 20_000, 40_000, 60_000, 80_000], + 'extra': ['h', 'i', 'j', 'k', 'l', 'm', 'n', 'o'] +}) +``` + +--- + +Si nous appliquons le pivot suivant sur ces données : + +```{.python .numberLines} +df2 = df.pivot(index='district', columns='country', values='population') +``` + +Nous obtiendrons les données suivantes : + +``` +country bel fra +district +east 40000 30000 +north 20000 10000 +south 80000 70000 +west 60000 50000 +``` + +--- + +L'argument `values` est facultatif. S'il est omis, alors le `DataFrame` résultant +contiendra des colonnes de type `MultiIndex`, un index dont les entrées sont composées de `tuples`. + +Ici, au lieu d'avoir deux colonnes de pays pour la colonne `population`, nous aurons deux colonnes de pays pour la colonne `population`, deux colonnes de pays pour la colonne `extra`, et ainsi de suite pour toutes les colonnes non utilisées en tant qu'index ou colonnes. + +Les noms de ces colonnes seront : + +``` +("population", "bel") +("population", "fra") +("extra", "bel") +("extra", "fra") +``` diff --git a/documentation/02-a-rest-services.md b/documentation/02-a-rest-services.md new file mode 100644 index 0000000..9bd2f53 --- /dev/null +++ b/documentation/02-a-rest-services.md @@ -0,0 +1,162 @@ +--- +title: Consommer des services REST +author: Steve Kossouho +--- + +# Consommer des services web REST + +--- + +## Une API REST, c'est quoi ? + +Une API REST proposée par un service web, c'est quoi, et à quoi ça sert ? +Voyons le scénario suivant : vous écrivez une application dans laquelle vous avez besoin +de données géographiques. Quelles sont les solutions possibles ? + +1. Ajouter les données manuellement en les récupérant depuis des sources diverses brutes +2. Exporter une source complète vers une base de données pour la réutiliser +3. Les fameux services web + +--- + +### Principe général d'un service web + +L'avantage du web, c'est que de nos jours, c'est facilement accessible. Accéder à un document +consiste généralement à taper dans un navigateur son URL. L'idée d'un service web est de proposer +sous forme de documents accessibles via un site, un ensemble de données qu'un développeur aurait du +mal à obtenir de façon organisée par ses propres moyens. + +C'est comme si vous aviez une base de données accessible depuis le web, que vous n'avez pas à maintenir, +et qu'en plus vous ne risquez pas de casser. + +--- + +### Principe d'une API REST + +![Exemple d'usage d'API REST](assets/images/rest-api-general.png) + +--- + +## Bibliothèques Python + +Il existe plusieurs bibliothèques Python pour consommer des API REST. Parmi les plus populaires, on trouve `requests` et `uplink`. + +La bibliothèque `requests` est une bibliothèque HTTP simple, capable de consommer une API REST. +Elle est extrêmement facile à utiliser et prend en charge toutes les méthodes HTTP (GET, POST, PUT, DELETE, etc.) + +Par exemple, pour faire une requête GET sur une API : + +```{.python .numberLines} +import requests + +response = requests.get('https://api.example.com/resources') +print(response.json()) +``` + +--- + +`uplink` est une bibliothèque qui vous permet de "transformer" une API REST en une classe Python. +Vous pouvez définir une interface qui correspond à l'API et `uplink` se charge de générer les requêtes HTTP pour vous. + +Voici un exemple d'utilisation de `uplink` : + +```{.python .numberLines} +from uplink import Consumer, get, Path + +class GitHubAPI(Consumer): + @get("/users/{username}") + def get_user(self, username: Path): + pass + +github = GitHubAPI(base_url="https://api.github.com") +user = github.get_user(username="example") +print(user) +``` + +--- + +## Consommer une API REST en Python + +Pour consommer une API REST en Python, on utilisera souvent la bibliothèque `requests`. +Supposons que nous voulons accéder à une API qui nous donne des informations sur des utilisateurs à partir de leur nom d'utilisateur. + +```{.python .numberLines} +import requests + +username = 'example' +response = requests.get(f'https://api.example.com/users/{username}') + +# Vérifier le statut de la requête +if response.status_code == 200: + user_data = response.json() # Convertir la réponse en JSON + print(user_data) +else: + print(f'Requête échouée avec le statut: {response.status_code}') +``` + +--- + +## Authentification pour les API REST + +De nombreuses API nécessitent une forme d'authentification pour accéder aux ressources. +Il existe plusieurs mécanismes d'authentification, mais l'un des plus courants est l'authentification basée sur les tokens. + +L'authentification basée sur les tokens signifie que vous devez envoyer un token spécial dans l'en-tête de votre requête HTTP. +Ce token est généralement généré lorsque vous vous connectez à l'API. + +--- + +### Jetons d'authentification + +La récupération d'un token d'authentification dépend beaucoup de l'API spécifique que vous utilisez. Toutefois, pour de nombreuses API, cela implique généralement d'envoyer une requête POST à un point de terminaison d'authentification, avec vos identifiants (par exemple, votre nom d'utilisateur et votre mot de passe). + +Voici un exemple général : + +```{.python .numberLines} +import requests +import json + +# URL du point de terminaison d'authentification +url = "https://api.example.com/auth" + +# Vos identifiants +credentials = { 'username': 'your-username', 'password': 'your-password'} + +# Envoi de la requête POST +response = requests.post(url, data=json.dumps(credentials)) + +# Vérification du statut de la requête +if response.status_code == 200: + data = response.json() # Convertir la réponse en JSON + token = data.get('token') # Extraction du token + print(token) +else: + print(f"Échec de l'authentification, statut: {response.status_code}") +``` + +--- + +N'oubliez pas de remplacer 'your-username', 'your-password' et l'URL par vos propres informations. Notez également que cet exemple suppose que l'API renvoie le token d'authentification sous forme de JSON avec la clé 'token'. Certaines API peuvent utiliser un schéma différent, vous devrez donc consulter la documentation de l'API pour les détails précis. + +Faites également attention à ne pas exposer vos identifiants en dur dans votre code, notamment si celui-ci est partagé ou stocké dans un dépôt de versionnement public. Envisagez plutôt l'utilisation de variables d'environnement ou de fichiers de configuration sécurisés pour stocker ces informations sensibles. + +--- + +### Utiliser une API avec jeton d'authentification + +Voici comment vous pouvez envoyer un token d'authentification avec `requests` : + +```{.python .numberLines} +headers = {'Authorization': 'Bearer '} + +response = requests.get('https://api.example.com/resources', headers=headers) +print(response.json()) +``` + +N'oubliez pas de remplacer 'your-token' par votre véritable jeton d'authentification. + +--- + +## Ressources pour s'entraîner + +- [DummyJSON](https://dummyjson.com/docs) diff --git a/documentation/02-library-basics.md b/documentation/02-library-basics.md new file mode 100644 index 0000000..910cf01 --- /dev/null +++ b/documentation/02-library-basics.md @@ -0,0 +1,196 @@ +# Introduction to Pandas for Beginners + +Pandas is a powerful library for data manipulation and analysis in Python. It provides easy-to-use data structures and data analysis tools for handling and manipulating numerical tables and time series data. This guide is intended for beginners with no prior experience in data analysis or Pandas. + +--- + +## Installation + +To use Pandas, you must first install it. You can do this by running the following command in your command line: + +```bash +pip install pandas +``` + +--- + +## Data Structures + +Pandas has two main data structures: `Series` and `DataFrame`. + +A `Series` is a one-dimensional array-like object that can hold any data type. It is similar to a column in a spreadsheet or a dataset in R. Here's an example of creating a series: + +```python {.numberLines} + +import pandas as pd + +data = [1, 2, 3, 4] +s = pd.Series(data) +print(s) +``` + +. . . + +A `DataFrame` is a two-dimensional table of data with rows and columns. It is similar to a spreadsheet or SQL table. + +--- + +Here's an example of creating a `DataFrame`: + +```{.python .numberLines} +import pandas as pd + +data = {'name': ['John', 'Jane', 'Sam'], + 'age': [30, 25, 35], + 'city': ['New York', 'San Francisco', 'Los Angeles']} +df = pd.DataFrame(data) +print(df) +``` + +--- + +## Data Analysis + +Pandas provides a variety of useful tools for data analysis. Here are a few examples: + +--- + +### Selection: Selecting specific columns or rows from a DataFrame. + +```{.python .numberLines} +import pandas as pd + +data = {'name': ['John', 'Jane', 'Sam'], + 'age': [30, 25, 35], + 'city': ['New York', 'San Francisco', 'Los Angeles']} +df = pd.DataFrame(data) + +# select a specific column +print(df['name']) + +# select rows by index +print(df.loc[1]) +``` + +--- + +### Filtering: Filtering rows based on a condition. + +```{.python .numberLines} +import pandas as pd + +data = {'name': ['John', 'Jane', 'Sam'], + 'age': [30, 25, 35], + 'city': ['New York', 'San Francisco', 'Los Angeles']} +df = pd.DataFrame(data) + +# filter rows where age is greater than 30 +print(df[df['age'] > 30]) +``` + +--- + +### Groupby: Grouping rows based on a column and applying a function to each group. + +```{.python .numberLines} +import pandas as pd + +data = {'name': ['John', 'Jane', 'Sam', 'John', 'Jane'], + 'age': [30, 25, 35, 40, 22], + 'city': ['New York', 'San Francisco', 'Los Angeles','New York', 'San Francisco']} +df = pd.DataFrame(data) + +# group by city and calculate mean age for each group +print(df.groupby('city').mean()) +``` + +These are just a few examples of the many things you can do with Pandas. Some other useful functionality includes: + +--- + +### Merging: Merging multiple DataFrames together on specific columns. + +```{.python .numberLines} +import pandas as pd + +data1 = {'name': ['John', 'Jane', 'Sam'], + 'age': [30, 25, 35], + 'city': ['New York', 'San Francisco', 'Los Angeles']} +data2 = {'name': ['Sam', 'Jane', 'John'], + 'gender': ['M', 'F', 'M']} + +df1 = pd.DataFrame(data1) +df2 = pd.DataFrame(data2) + +# merge two dataframes on name column +merged_df = pd.merge(df1, df2, on='name') +print(merged_df) +``` + +--- + +### Sorting: Sorting a DataFrame by one or multiple columns. + +```{.python .numberLines} +import pandas as pd + +data = {'name': ['John', 'Jane', 'Sam'], + 'age': [30, 25, 35], + 'city': ['New York', 'San Francisco', 'Los Angeles']} +df = pd.DataFrame(data) + +# sort dataframe by age in ascending order +df.sort_values(by='age', ascending=True) +``` + +--- + +### Data Cleaning: Handling missing values and duplicates. + +```{.python .numberLines} +import pandas as pd + +data = {'name': ['John', 'Jane', 'Sam', None], + 'age': [30, 25, 35, None], + 'city': ['New York', 'San Francisco', 'Los Angeles', 'New York']} +df = pd.DataFrame(data) + +# drop rows with missing values +df.dropna() + +# drop duplicate rows +df.drop_duplicates() +``` + +--- + +### Here's a simple example of how to rename a column in a Pandas DataFrame: + +```{.python .numberLines} +import pandas as pd + +# Create a sample dataframe +data = {'name': ['John', 'Jane', 'Sam'], + 'age': [30, 25, 35], + 'city': ['New York', 'San Francisco', 'Los Angeles']} +df = pd.DataFrame(data) + +# Print the original dataframe +print(df) + +# Rename 'name' column to 'username' +df.rename(columns={'name': 'username'}, inplace=True) + +# Print the dataframe after renaming +print(df) +``` + +You can also rename multiple columns at once by passing a dictionary of old to new column names. + +```{.python .numberLines} +df.rename(columns={'age': 'Age','city': 'City'}, inplace=True) +``` + +The inplace=True argument makes the change permanent and updates the DataFrame in place. If you don't want to modify the original DataFrame and want to return a new DataFrame with the changes, you can set inplace=False or not include the argument at all. + +With this library, you will be able to handle and analyze large datasets with ease. The documentation is a great resource for learning more about the capabilities of the library. diff --git a/documentation/03-b-seaborn-intro.md b/documentation/03-b-seaborn-intro.md new file mode 100644 index 0000000..fcfc55b --- /dev/null +++ b/documentation/03-b-seaborn-intro.md @@ -0,0 +1,78 @@ +## Introduction à Seaborn + +--- + +### Qu'est-ce que Seaborn ? + +[Seaborn](https://seaborn.pydata.org/) est une bibliothèque de visualisation de données en Python basée sur Matplotlib. Elle offre une interface de haut niveau pour créer des graphiques attrayants et informatifs. Elle est particulièrement utile pour visualiser des données complexes, comme celles provenant de DataFrames pandas. + +--- + +### Afficher des diagrammes avec Seaborn + +Seaborn propose plusieurs types de diagrammes, comme les nuages de points, les histogrammes, les diagrammes à barres, etc. Chaque type de diagramme est adapté à un type particulier de données et permet d'en extraire différents types d'informations. + +--- + +#### Afficher un nuage de points + +Un nuage de points est un type de diagramme qui permet de visualiser la relation entre deux variables continues. Avec Seaborn, vous pouvez créer un nuage de points en utilisant la fonction `scatterplot()` : + +```{.python .numberLines} +import seaborn as sns + +# Supposons que nous ayons un DataFrame pandas df avec deux colonnes 'x' et 'y' +sns.scatterplot(x='x', y='y', data=df) +``` + +--- + +#### Afficher un histogramme avec facettes + +Un histogramme est un type de diagramme qui permet de visualiser la distribution d'une variable continue. Avec Seaborn, vous pouvez créer un histogramme avec facettes (c'est-à-dire, plusieurs histogrammes pour différentes catégories de données) en utilisant la fonction `FacetGrid()` : + +```{.python .numberLines} +# Supposons que nous ayons un DataFrame pandas df avec une colonne 'x' et une colonne catégorielle 'cat' +g = sns.FacetGrid(df, col='cat') +g.map(sns.histplot, 'x') +``` + +--- + +#### Afficher un histogramme en barres avec facettes + +Un histogramme en barres est un type de diagramme qui permet de visualiser la distribution d'une variable catégorielle. Avec Seaborn, vous pouvez créer un histogramme en barres avec facettes en utilisant les fonctions `FacetGrid()` et `barplot()` : + +```{.python .numberLines} +# Supposons que nous ayons un DataFrame pandas df avec une colonne 'x' et deux colonnes catégorielles 'cat1' et 'cat2' +g = sns.FacetGrid(df, col='cat1') +g.map(sns.barplot, 'cat2', 'x') +``` + +--- + +### Configurer le thème des graphiques Seaborn + +Seaborn offre plusieurs options pour personnaliser l'apparence de vos graphiques. + +--- + +#### Styles des graphiques + +Seaborn propose cinq thèmes prédéfinis : `darkgrid`, `whitegrid`, `dark`, `white`, et `ticks`. Vous pouvez les appliquer en utilisant la fonction `set_style()` : + +```{.python .numberLines} +sns.set_style('whitegrid') +``` + +--- + +#### Définir des palettes de couleurs + +Seaborn offre plusieurs palettes de couleurs pour personnaliser l'apparence de vos graphiques. Vous pouvez les utiliser en passant le nom de la palette à la fonction `color_palette()` : + +```{.python .numberLines} +sns.set_palette(sns.color_palette('Blues')) +``` + +Ainsi, Seaborn vous permet de créer facilement des graphiques attrayants et informatifs en Python. diff --git a/documentation/03-c-plotly-intro.md b/documentation/03-c-plotly-intro.md new file mode 100644 index 0000000..402221f --- /dev/null +++ b/documentation/03-c-plotly-intro.md @@ -0,0 +1,149 @@ +# Introduction à Plotly pour Python + +--- + +## Qu'est-ce que Plotly ? + +Plotly est une bibliothèque open-source de visualisation de données en Python. Elle permet de produire des graphiques interactifs de haute qualité directement depuis Python, et supporte de nombreux types de graphiques, y compris des graphiques 3D et des cartes géographiques. + +--- + +## Afficher des diagrammes avec Plotly + +Plotly propose une variété de diagrammes pour visualiser les données. Voici quelques exemples sur comment afficher des nuages de points, des histogrammes et des histogrammes en barres avec facettes. + +--- + +### Afficher un nuage de points + +Pour créer un nuage de points avec Plotly, vous pouvez utiliser la classe `Scatter` de `plotly.graph_objects` : + +```{.python .numberLines} +import plotly.graph_objects as go + +# Supposons que nous ayons deux listes x et y +fig = go.Figure(data=go.Scatter(x=x, y=y, mode='markers')) +fig.show() +``` + +L'utilisation classique de Plotly consiste à créer un objet de type `Figure` représentant un graphique, puis d'y associer un type de diagramme. L'affichage du graphique via la méthode `show()` lance un serveur web simple et le navigateur par défaut de votre système pour afficher le contenu. + +--- + +![Exemple de diagramme de base Plotly](assets/images/plotly-chart-bar.png) + +Notez que ce genre de diagramme est rendu interactif graĉe aux technologies du web; vous pouvez ainsi zoomer ou passer votre souris sur des sections pour observer des boîtes descriptives des données. + +--- + +### Afficher un histogramme avec facettes + +Pour créer un histogramme avec facettes avec Plotly, vous pouvez utiliser la classe `Histogram` et préparer un graphique à multiples diagrammes via `make_subplots` : + +```{.python .numberLines} +from plotly.subplots import make_subplots + +# Nous avons un DataFrame df avec une colonne 'value' et une colonne 'category' +fig = make_subplots(rows=1, cols=2) +# Parcourir toutes les catégories différentes de la colonne catégorie +for i, cat in enumerate(df['category'].unique()): + # Et ajouter un diagramme en barres pour les valeurs de la catégorie + fig.add_trace(go.Histogram(x=df[df['category']==cat]['value']), row=1, col=i+1) +fig.show() +``` + +--- + +### Afficher un histogramme en barres avec facettes + +Pour créer un histogramme en barres avec facettes avec Plotly, vous pouvez utiliser la classe `Bar` et la fonction `subplot` : + +```{.python .numberLines} +from plotly.subplots import make_subplots + + Supposons que nous ayons un DataFrame pandas df avec une colonne 'value' et deux colonnes catégorielles 'category1' et 'category2' +fig = make_subplots(rows=1, cols=2) +for i, cat in enumerate(df['category1'].unique()): + fig.add_trace(go.Bar(x=df[(df['category1']==cat)]['category2'], y=df[(df['category1']==cat)]['value']), row=1, col=i+1) +fig.show() +``` + +--- + +### Types de graphiques réalisables avec Plotly + +Les quelques exemples précédents ont permis de montrer la simplicité d'utilisation de Ploty/Dash pour une variété de diagrammes, mais selon vos besoins, il en existe de nombreux autres, décrits dans la page officielle suivante : + +[Plotly Charts](https://plotly.com/python/basic-charts/) + +--- + +La majorité des diagrammes réalisables avec Plotly sont importables en tant que classes dans le package `plotly.express`. + +--- + +### Diagramme en barres simple + +Si vous avez un `DataFrame` simple avec deux colonnes, par exemple une colonne d'identifiants et une colonne de valeurs associées, vous pouvez très simplement afficher un diagramme en barres avec Plotly : + +```python +import pandas as pd +from plotly.express import bar + +df = pd.DataFrame(data={"label": ["Citron", "Pomme", "Mangue"], "price": [1.99, 3.97, 6.8]}) +plot = bar(df, x="label", y="price") +plot.show() +``` + +--- + +### Diagramme en secteurs groupés + +Si vos données contiennent des informations qui peuvent être regroupées en sous-catégories (par ex. vous avez des ventes par ville, et plusieurs villes peuvent appartenir au même pays), un diagramme en secteurs à plusieurs anneaux peut vous permettre d'explorer vos données de façon intéressante : + + +```python +import pandas as pd +from plotly.express import sunburst + +df = pd.DataFrame(data={ + "country": ["France", "France", "Spain", "Spain"], + "city": ["Montpellier", "Bordeaux", "Madrid", "Valencia"], + "sales": [150_000, 127_000, 97_200, 137_250] +}) +plot = sunburst(df, path=["country", "city"], values="sales") +plot.show() +``` + +--- + +## Configurer le thème des graphiques Plotly + +Avec Plotly, vous pouvez personnaliser l'apparence de vos graphiques en utilisant des thèmes. + +--- + +### Styles des graphiques + +Plotly offre plusieurs thèmes prédéfinis que vous pouvez appliquer à vos graphiques : + +```{.python .numberLines} +import plotly.io as pio + +pio.templates.default = "plotly_dark" # Exemple avec le thème "plotly_dark" +``` + +--- + +### Définir des palettes de couleurs + +Vous pouvez également personnaliser les couleurs utilisées dans vos graphiques avec Plotly : + +```{.python .numberLines} +fig = go.Figure( + data=go.Scatter(x=x, y=y, mode='markers', marker=dict(color='rgba(152, 0, 0, .8)')) +) +fig.show() +``` + +Dans cet exemple, nous avons utilisé la propriété `marker` pour changer la couleur des points dans le nuage de points. diff --git a/documentation/03-d-dash-intro.md b/documentation/03-d-dash-intro.md new file mode 100644 index 0000000..014329c --- /dev/null +++ b/documentation/03-d-dash-intro.md @@ -0,0 +1,118 @@ +## Introduction à Plotly/Dash pour Python + +--- + +### Qu'est-ce que Dash ? + +Dash est un outil associé à Plotly, permettant de générer des tableaux de bord web en Python. + +--- + +### Afficher des diagrammes avec Plotly + +Dash peut intégrer les diagrammes générés avec Plotly facilement. Dash existe dans deux versions : + +- la version gratuite permet de créer programmatiquement ses propres tableaux de bord +- la version Entreprise, payante propose des outils visuels avancés de personnalisation de tableau de bord + +--- + +### Installer Dash + +Dash et Plotly sont disponibles sur le dépôt officiel [PyPI](https://pypi.org/). Il suffit donc d'ouvrir le terminal, +_en ayant activé votre environnement virtuel si nécessaire_, et de taper : + +```bash +pip install plotly dash +``` + +--- + +#### Afficher un tableau HTML contenant un `DataFrame` + +Dash permet simplement d'afficher un tableau paginé, représentant le contenu d'un DataFrame : + +```{.python .numberLines} +from dash import Dash, html, dash_table +import pandas as pd + +df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/gapminder2007.csv') + +# Créer une application Dash +app = Dash(__name__) + +# Configurer la mise en page +app.layout = html.Div([ + html.Div(children='My First App with Data'), + dash_table.DataTable(data=df.to_dict('records'), page_size=10) +]) + +# Créer un serveur web pour afficher le résultat +if __name__ == '__main__': + app.run(debug=True) +``` + +--- + +#### Afficher un histogramme avec facettes + +Pour créer un histogramme avec facettes avec Plotly, vous pouvez utiliser la classe `Histogram` et la fonction `subplot` : + +```{.python .numberLines} +from plotly.subplots import make_subplots + +# Supposons que nous ayons un DataFrame pandas df avec une colonne 'value' et une colonne catégorielle 'category' +fig = make_subplots(rows=1, cols=2) +for i, cat in enumerate(df['category'].unique()): + fig.add_trace(go.Histogram(x=df[df['category']==cat]['value']), row=1, col=i+1) +fig.show() +``` + +--- + +#### Afficher un histogramme en barres avec facettes + +Pour créer un histogramme en barres avec facettes avec Plotly, vous pouvez utiliser la classe `Bar` et la fonction `subplot` : + +```{.python .numberLines} +from plotly.subplots import make_subplots + +# Supposons que nous ayons un DataFrame pandas df avec une colonne 'value' et deux colonnes catégorielles 'category1' et 'category2' +fig = make_subplots(rows=1, cols=2) +for i, cat in enumerate(df['category1'].unique()): + fig.add_trace(go.Bar(x=df[(df['category1']==cat)]['category2'], y=df[(df['category1']==cat)]['value']), row=1, col=i+1) +fig.show() +``` + +--- + +### Configurer le thème des graphiques Plotly + +Avec Plotly, vous pouvez personnaliser l'apparence de vos graphiques en utilisant des thèmes. + +--- + +#### Styles des graphiques + +Plotly offre plusieurs thèmes prédéfinis que vous pouvez appliquer à vos graphiques : + +```{.python .numberLines} +import plotly.io as pio + +pio.templates.default = "plotly_dark" # Exemple avec le thème "plotly_dark" +``` + +--- + +#### Définir des palettes de couleurs + +Vous pouvez également personnaliser les couleurs utilisées dans vos graphiques avec Plotly : + +```{.python .numberLines} +fig = go.Figure( + data=go.Scatter(x=x, y=y, mode='markers', marker=dict(color='rgba(152, 0, 0, .8)')) +) +fig.show() +``` + +Dans cet exemple, nous avons utilisé la propriété `marker` pour changer la couleur des points dans le nuage de points. diff --git a/documentation/03-exploratory-analysis.md b/documentation/03-exploratory-analysis.md new file mode 100644 index 0000000..5ae6e4d --- /dev/null +++ b/documentation/03-exploratory-analysis.md @@ -0,0 +1,364 @@ +--- +title: Analyse exploratoire des données +author: Steve Kossouho +--- + +# Analyse exploratoire des données + +Dans le domaine de la science des données, l'Analyse exploratoire des données (AED) est une approche essentielle. Elle est utilisée pour comprendre la nature, la structure et la distribution des données en utilisant différentes techniques et outils. Python, avec ses nombreuses bibliothèques dédiées, s'avère être un outil puissant pour l'AED. + +--- + +## Métriques d’analyse + +Pour analyser un ensemble de données, nous avons souvent besoin de calculer diverses métriques qui fournissent des informations précieuses. Dans un objet `DataFrame` de la bibliothèque Pandas, ces calculs sont simples à réaliser. + +Prenons un exemple au hasard : un ensemble de données contenant les ventes d'un magasin. Les colonnes pourraient être "produit", "quantité vendue" et "prix unitaire". Nous pourrions vouloir connaître le produit le plus vendu, ou le produit générant le plus de revenus. + +--- + +Voici un extrait de code illustrant ce calcul : + +```{.python .numberLines} +import pandas as pd + +# Supposons que df est notre DataFrame +produit_plus_vendu = df['quantité vendue'].idxmax() +produit_plus_rentable = (df['quantité vendue'] * df['prix unitaire']).idxmax() + +print(produit_plus_vendu, produit_plus_rentable) +``` + +--- + +## Visualisation des données + +La visualisation des données est un élément crucial de l'analyse exploratoire des données. Elle permet de représenter les données de manière graphique, ce qui facilite la compréhension de leurs structures et patterns. + +Une bibliothèque couramment utilisée pour la visualisation des données en Python est Matplotlib, mais Pandas propose également des méthodes de tracé intégrées qui sont très utiles. Par exemple, pour tracer un histogramme de la colonne "quantité vendue" de notre DataFrame de vente, nous pouvons faire : + +```{.bash .numberLines} +pip install matplotlib # dans un terminal +pip install pyside6 # nécessaire pour afficher une fenetre +``` + +```{.python .numberLines} +from matplotlib import pyplot as plt +import pandas as pd + +df = ... # notre dataframe + +# Si notre dataframe contient des données, afficher un histogramme +df.plot.bar(x="produit", y="quantité vendue") +plt.show() +``` + +--- + +### Documentation officielle des méthodes de diagrammes + +[Voir DataFrame.plot](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.plot.html) + +--- + +### Petit exemple pour l'histogramme + +```{.python .numberLines} +from matplotlib import pyplot as plt +import pandas as pd +# Créer un petit DataFrame +df = pd.DataFrame(data={ + "produit": ["pomme", "poire", "raisin"], + "prix": [3, 2.79, 5.49] +}) +# Créer le diagramme puis l'afficher +df.plot.bar(x="produit", y="prix") +plt.show() +``` + +--- + +## Génération de graphes (Matplotlib) + +Matplotlib est une bibliothèque de Python dédiée à la création de graphiques statiques, animés et interactifs. Elle offre une grande flexibilité pour personnaliser les graphiques selon les besoins spécifiques de l'utilisateur. + +Pour utiliser Matplotlib, il faut d'abord l'installer. Si vous utilisez pip comme gestionnaire de paquets, vous pouvez l'installer en utilisant la commande suivante dans votre terminal : + +```{.bash .numberLines} +pip install matplotlib +``` + +--- + +Une fois installée, Matplotlib peut être utilisée pour générer divers types de graphiques. Par exemple, pour générer un diagramme à barres montrant la quantité vendue pour chaque produit, nous pourrions faire : + +```{.python .numberLines} +import matplotlib.pyplot as plt + +produits = df['produit'].unique() +quantités = [df[df['produit'] == produit]['quantité vendue'].sum() for produit in produits] + +plt.bar(produits, quantités) +plt.xlabel('Produits') +plt.ylabel('Quantité Vendue') +plt.show() +``` + +--- + +Dans ce code, nous récupérons d'abord la liste unique des produits, puis calculons la quantité totale vendue pour + + chaque produit. Enfin, nous utilisons `plt.bar` pour créer le diagramme à barres, et `plt.show()` pour afficher le graphique. + +--- + +## Création de graphiques à barres avec Matplotlib + +Les graphiques à barres sont particulièrement utiles pour comparer des quantités relatives entre différentes catégories. Prenons l'exemple d'un ensemble de données contenant le nombre de ventes réalisées par chaque vendeur dans une entreprise. + +```{.python .numberLines} +import matplotlib.pyplot as plt + +# Supposons que nous ayons une liste de vendeurs et leurs ventes respectives +vendeurs = ['Alice', 'Bob', 'Charlie', 'Dave'] +ventes = [123, 156, 98, 127] + +plt.bar(vendeurs, ventes) +plt.xlabel('Vendeurs') +plt.ylabel('Ventes') +plt.title('Nombre de ventes par vendeur') +plt.show() +``` + +--- + +## Construction de graphiques à secteurs (camembert) avec Matplotlib + +Les graphiques à secteurs (ou camembert) sont idéals pour illustrer la proportion d'éléments dans un ensemble. + +Supposons que nous voulions visualiser la proportion des ventes totales réalisées par chaque vendeur. + +```{.python .numberLines} +plt.pie(ventes, labels=vendeurs, autopct='%1.1f%%') +plt.title('Proportion des ventes par vendeur') +plt.show() +``` + +--- + +## Tracé de courbes avec Matplotlib + +Les courbes sont un outil indispensable pour représenter l'évolution d'une quantité en fonction d'une autre. Imaginons que nous souhaitions tracer l'évolution des ventes d'un produit au fil du temps. + +```{.python .numberLines} +# Supposons que nous ayons une liste de mois et les ventes correspondantes +mois = ['Jan', 'Fév', 'Mar', 'Avr', 'Mai', 'Juin'] +ventes = [110, 120, 115, 125, 150, 180] + +plt.plot(mois, ventes) +plt.xlabel('Mois') +plt.ylabel('Ventes') +plt.title('Evolution des ventes au fil des mois') +plt.show() +``` + +--- + +## Tracé de nuages de points avec Matplotlib + +Les nuages de points sont excellents pour visualiser la corrélation entre deux variables quantitatives. Par exemple, si nous avions des données sur le prix d'un produit et le nombre de ces produits vendus, nous pourrions vouloir voir si ces deux variables sont corrélées. + +```{.python .numberLines} +# Supposons que nous ayons une liste de prix et une liste correspondante de quantités vendues +prix = [5, 10, 15, 20, 25, 30] +quantités_vendues = [105, 95, 90, 85, 80, 75] + +plt.scatter(prix, quantités_vendues) +plt.xlabel('Prix') +plt.ylabel('Quantités vendues') +plt.title('Corrélation entre le prix et la quantité vendue') +plt.show() +``` + +--- + +### Deuxième exemple de nuage de points + +Dans cet exemple, on génère des coordonnées de points, disséminés autour d'une droite. L'objectif est ensuite d'en détecter une régression linéaire (et en afficher un segment dans le graphique). + +--- + +#### Première étape : Générer les coordonnées + +```{.python .numberLines} +# On importe le nécessaire +import pandas as pd # pour le dataframe et le nuage de points +import numpy as np # pour la fonction de régression polynomiale +from random import random # pour générer des valeurs aléatoires +from matplotlib import pyplot as plt # pour afficher le graphique + +# Créer des listes pour avoir les valeurs des deux axes +x = [] +y = [] + +# Faire une boucle de 30 itérations pour créer des coordonnées pour X et Y +for nombre in range(30): + x.append(nombre) + y.append(nombre - 5 + random() * 10) # x - 5 <= y <= x + 5 + +# Convertir x et y en tableaux compatibles avec numpy et pandas +# Sans ça, je ne pourrai pas utiliser x et y pour dessiner la ligne de la régression linéaire +x = np.array(x) +y = np.array(y) +``` + +--- + +#### Deuxième étape : Construire un `DataFrame` et générer le diagramme + +```{.python .numberLines} +... # Reprendre le code précédent + +# Créer un dataframe avec x et y +df = pd.DataFrame(data={"X": x, "Y": y}) + +# Afficher un nuage de points (scatter plot) +chart = df.plot.scatter(x="X", y="Y") +``` + +--- + +### Troisième étape : Calculer la régression et générer la ligne + +```{.python .numberLines} +... # Reprendre le code précédent + +# Pour afficher une ligne représentant la régression linéaire des points +# L'argument "1" indique le degré de la régression (premier degré donc linéaire). +# On récupère chaque coeff pour chaque degré de la régression. +coeff_niv1, constant = np.polyfit(x, y, 1) +plt.plot(x, coeff_niv1 * x + constant, color="orange") +plt.show() # Afficher le résultat +``` + +--- + +On aurait pu faire une régression du second degré plus ou moins de la même manière : + +```{.python .numberLines} +coeff_niv2, coeff_niv1, constant = np.polyfit(x, y, 2) +plt.plot(x, coeff_niv2 * (x ** 2) + coeff_niv1 * x + constant, color="orange") +plt.show() # Afficher le résultat +``` + +Et on aurait une courbe (normalement très légèrement incurvée, qui ressemble à la régression linéaire) + +--- + +## Tracé de diagramme à moustache (boîte) + +En temps normal, un diagramme à moustache est une représentation de la dispersion des éléments d'une série, découpée en quartiles. + +Pour afficher un `DataFrame` en diagramme à moustache, voici un exemple : + +```{.python .numberLines} +import pandas as pd +from matplotlib import pyplot as plt + +# Créer un DataFrame avec deux séries de valeurs dans des colonnes A et B +df = pd.DataFrame(data={ + "A": [1, 9, 11, 17, 6, 11, 14, 1], + "B": [20, 11, 13, 14, 15, 18, 14, 9] +}) + +# Tout simplement générer et afficher le graphique +df.plot.box() +plt.show() +``` + +--- + +### Diagramme à moustache et valeurs aberrantes (outliers) + +Il peut arriver que le tracé d'un diagramme à moustache contienne des petits +cercles. Ces cercles représentent les valeurs des colonnes considérées comme aberrantes (statistiquement trop éloignées des autres valeurs). + +L'exemple suivant le teste : + +```{.python .numberLines} +import pandas as pd +from matplotlib import pyplot as plt + +# Créer un DataFrame avec deux séries de valeurs dans des colonnes A et B +# Beaucoup de valeurs sont proches les unes des autres, celles éloignées seront affichées en cercle, en dehors de la moustache. +df = pd.DataFrame(data={ + "A": [1, 9, 41, 11, 25, 6, 81, 11, 14, 1], + "B": [125, 40, 71, 11, 13, 14, 15, 18, 14, 1] +}) + +# Tout simplement générer et afficher le graphique +df.plot.box() +plt.show() +``` + +--- + +### Comment savoir si une valeur est aberrante + +![Valeurs et groupes dans les diagrammes à moustache](assets/images/charts-box-outliers.png) + +--- + +## Diagramme en bougie (cours de bourse et tendances) + +![Diagramme en bougie](assets/images/charts-candlebox-plotly.png) + +--- + +Les diagrammes en bougie sont en général utilisés pour donner des informations sur le suivi des cours de bourse (permettent simplement de voir pour des périodes données, les valeurs d'un cours en début et fin de période, ainsi que les valeurs extrêmes d'un cours durant lesdites périodes). + +Matplotlib n'est pas capable de générer ce genre de diagramme, mais `plotly` +le peut (et c'est gratuit). + +--- + +### Plotly + +[Plotly](https://plotly.com/python/) est une bibliothèque très compétente et disponible avec Python pour générer des graphiques. + +Pour installer la bibliothèque, il suffit dans un terminal : + +```{.bash .numberLines} +pip install plotly +``` + +--- + +### Exemple avec Plotly + +Supposons que j'ai dans un `DataFrame` des données de cours sur uniquement trois jours (avec des infos sur le cours à l'ouverture de la période, à la fermeture, et les extrêmes sur la même période) : + +```{.python .numberLines} +import pandas as pd +import plotly.graph_objects as go + +# Créer un Dataframe avec les données +df = pd.DataFrame(data={ + "day": ["2023-05-10", "2023-05-11", "2023-05-12"], + "open": [10, 15, 16], + "close": [14, 15.9, 13.8], + "high": [14, 16.8, 16.05], + "low": [9.97, 15, 13.7] +}) +# Convertir la colonne de date dans le type datetime +df["day"] = pd.to_datetime(df["day"]) + +# Générer un diagramme Plotly qui contient le graphique en bougie +# Dans les arguments de la classe de diagramme Candlestick, les arguments +# x, open, high, low, et close vont recevoir respectivement les données des +# colonnes correspondantes de notre DataFrame. +fig = go.Figure(data=[ + go.Candlestick(x=df['day'], open=df['open'], high=df['high'], low=df['low'], close=df['close'])]) +# Afficher le diagramme, qui sera affiché dans un navigateur web +fig.show() +``` diff --git a/documentation/04-financial-analysis.md b/documentation/04-financial-analysis.md new file mode 100644 index 0000000..c6cf740 --- /dev/null +++ b/documentation/04-financial-analysis.md @@ -0,0 +1,113 @@ +--- +title: Analyse exploratoire des données +author: Steve Kossouho +--- + +# Analyse financière avec Python + +L'analyse financière est une discipline cruciale qui aide les investisseurs, les entreprises et les gouvernements à prendre des décisions éclairées en analysant des données financières historiques et prévisionnelles. Python est largement utilisé dans l'analyse financière grâce à son écosystème riche en bibliothèques et outils. + +--- + +### Analyse financière : concepts de base + +L'analyse financière comprend l'étude des états financiers, la création de projections financières, la valorisation des entreprises, l'analyse des coûts, des bénéfices, des ventes, des revenus, etc. En utilisant ces données, on peut déterminer la santé financière d'une entreprise et prendre des décisions stratégiques éclairées. + +```{.python .numberLines} +# Prenons un exemple simple d'analyse de ratio financier + +# Ratio de liquidité courante = Actif courant / Passif courant +actif_courant = 100000 +passif_courant = 50000 +ratio_liquidite = actif_courant / passif_courant +print(f"Ratio de liquidité courante: {ratio_liquidite}") +``` + +--- + +### Panorama des bibliothèques Python : NumPy, SciPy, IPython (Jupyter) + +Python est doté d'un écosystème de bibliothèques incroyablement robuste qui facilite l'analyse financière. Les plus populaires sont NumPy, pour le calcul numérique, SciPy, pour les calculs scientifiques, et IPython (Jupyter), qui est un environnement interactif de programmation. + +Pour les installer, utilisez pip dans votre terminal : + +```shell +pip install numpy scipy jupyter +``` + +--- + +### Calcul matriciel (Numpy) + +NumPy est une bibliothèque Python qui fournit un support pour les grands tableaux multidimensionnels et les matrices, ainsi que pour une large collection de fonctions mathématiques de haut niveau. + +```{.python .numberLines} +import numpy as np + +# Création d'une matrice 2x2 +A = np.array([[1, 2], [3, 4]]) + +# Calcul de l'inverse de la matrice +A_inv = np.linalg.inv(A) +print(f"Matrice inverse:\n{A_inv}") +``` + +--- + +### Statistiques Descriptives (SciPy) + +SciPy est une bibliothèque Python utilisée pour les calculs scientifiques et techniques. Elle fournit de nombreuses fonctionnalités pour l'analyse statistique. + +```{.python .numberLines} +from scipy import stats + +# Supposons que nous ayons un ensemble de données de rendements financiers +rendements = [0.01, 0.02, -0.01, -0.02, 0.015, -0.02] + +# Nous pouvons calculer la moyenne et l'écart type avec SciPy +moyenne = stats.tmean(rendements) +ecart_type = stats.tstd(rendements) + +print(f"Moyenne: {moyenne}, Ecart-type: {ecart_type}") +``` + +--- + +### Comparaison de populations, mesures d’association (SciPy) + +SciPy fournit également des outils pour comparer des populations et mesurer des associations. Par exemple, on peut utiliser le test t de Student pour comparer les moyennes de deux échantillons indépendants. + +```{.python .numberLines} +from scipy import stats +# Supposons que nous ayons deux ensembles de données de rendements financiers +rendements_A = [0.01, 0.02, -0.01, -0.02, 0.015, -0.02] +rendements_B = [0.015, 0.025, -0.015, -0.025, 0.02, -0.03] + +# Nous pouvons effectuer un test t de Student pour comparer les moyennes +t_stat, p_val = stats.ttest_ind(rendements_A, rendements_B) + +print(f"Statistique de test t: {t_stat}, p-valeur: {p_val}") +``` + +Dans ce code, la statistique de test t et la p-valeur sont calculées. Si la p-valeur est inférieure à un certain seuil (généralement 0.05), alors on peut conclure qu'il y a une différence significative entre les moyennes des deux échantillons. + +--- + +### Analyse de variance (ANOVA) + +L'analyse de variance (ANOVA) est une technique statistique qui est utilisée pour comparer les moyennes de deux groupes ou plus pour déterminer si elles sont significativement différentes. ANOVA est utile lorsque vous voulez comparer plus de deux groupes à la fois, ce qui serait compliqué à réaliser avec des tests t. + +```{.python .numberLines} +from scipy import stats +# Supposons que nous ayons trois ensembles de données de rendements financiers +rendements_A = [0.01, 0.02, -0.01, -0.02, 0.015, -0.02] +rendements_B = [0.015, 0.025, -0.015, -0.025, 0.02, -0.03] +rendements_C = [0.02, 0.03, -0.02, -0.03, 0.025, -0.04] + +# Nous pouvons effectuer une ANOVA pour comparer les moyennes +f_stat, p_val = stats.f_oneway(rendements_A, rendements_B, rendements_C) + +print(f"Statistique de test F: {f_stat}, p-valeur: {p_val}") +``` + +Dans cet exemple, la statistique de test F et la p-valeur sont calculées. Si la p-valeur est inférieure à un certain seuil (généralement 0.05), alors on peut conclure qu'il y a une différence significative entre les moyennes des trois échantillons. diff --git a/documentation/05-celery-basics.md b/documentation/05-celery-basics.md new file mode 100644 index 0000000..7e87e62 --- /dev/null +++ b/documentation/05-celery-basics.md @@ -0,0 +1,196 @@ +--- +title: Celery +author: Steve Kossouho +--- + +# Gestion de tâches avec Celery + +--- + +## Qu'est-ce que Celery ? + +Celery est une bibliothèque Python open source qui est utilisée pour la gestion asynchrone de tâches. Il permet de distribuer l'exécution des tâches sur plusieurs travailleurs (workers), que l'on peut répartir sur plusieurs machines ou sur un même serveur. + +Grâce à sa capacité de parallélisation, Celery est un excellent choix pour réaliser des tâches longues et coûteuses telles que le traitement de gros volumes de données avec Pandas. + +--- + +## Configuration de Celery + +Pour commencer à utiliser Celery, vous devez tout d'abord installer le paquet via pip : + +```{.bash .numberLines} +pip install celery +``` + +Ensuite, vous aurez besoin d'un broker de messages, qui est utilisé par Celery pour passer les messages entre votre application principale et les travailleurs. Celery supporte plusieurs brokers de messages, mais nous utiliserons ici Redis pour sa simplicité d'utilisation. + +```{.bash .numberLines} +pip install redis +``` + +Une fois Redis installé, vous pouvez configurer Celery pour l'utiliser comme broker. Pour cela, vous devez créer une instance de Celery dans votre application : + +```{.python .numberLines} +from celery import Celery + +app = Celery('myapp', broker='redis://localhost:6379/0') +``` + +--- + +## Utilisation de Celery pour paralléliser des calculs avec Pandas + +Pour utiliser Celery pour paralléliser des calculs avec Pandas, vous devez d'abord définir des tâches. Une tâche est une fonction qui est exécutée de manière asynchrone. Par exemple, disons que vous ayez une fonction qui effectue un calcul sur un DataFrame Pandas : + +```{.python .numberLines} +def calculate(df): + return df.sum() +``` + +Vous pouvez transformer cette fonction en une tâche Celery en utilisant le décorateur `app.task` : + +```{.python .numberLines} +@app.task +def calculate(df): + return df.sum() +``` + +Maintenant, vous pouvez exécuter cette fonction de manière asynchrone en utilisant la méthode `.delay()` : + +```{.python .numberLines} +result = calculate.delay(df) +``` + +La méthode `.delay()` renvoie un objet `AsyncResult` que vous pouvez utiliser pour obtenir le résultat de la tâche une fois qu'elle est terminée : + +```{.python .numberLines} +print(result.get()) +``` + +--- + +## Utilisation de Celery pour planifier des tâches avec Pandas + +Celery fournit également un moyen de planifier l'exécution des tâches. Pour cela, vous aurez besoin de l'extension Celery Beat. + +Avec Celery Beat, vous pouvez définir des intervalles de temps réguliers pour l'exécution des tâches. Par exemple, vous pouvez configurer une tâche pour qu'elle soit exécutée toutes les 10 minutes. + +--- + +Voici comment vous pouvez configurer Celery Beat pour exécuter une tâche toutes les 10 minutes : + +```{.python .numberLines} +from celery.schedules import crontab + +app.conf.beat_schedule = { + 'run-every-10-minutes': { + 'task': 'myapp.calculate', + 'schedule': crontab(minute='*/10'), + }, +} +``` + +Dans cet exemple, `myapp.calculate` est le nom de la tâche que vous souhaitez exécuter. L'objet `crontab` est utilisé pour définir l'intervalle de temps pour l'exécution de la tâche. + +--- + +Celery est une bibliothèque puissante qui, associée à Pandas, peut grandement faciliter la gestion de tâches lourdes et coûteuses en termes de calcul. + +--- + +# Alternatives à Celery + +--- + +## RQ (Redis Queue) + +[RQ](https://python-rq.org/) est une bibliothèque Python simple pour la mise en file d'attente des tâches. Elle utilise Redis comme système de file d'attente. RQ est particulièrement apprécié pour sa simplicité et sa clarté, il est donc plus facile à apprendre et à mettre en œuvre que Celery. + +--- + +## Dramatiq + +[Dramatiq](https://dramatiq.io/) est une bibliothèque Python de mise en file d'attente de tâches distribuées avec un accent sur la simplicité, la fiabilité et la performance. Comme Celery, Dramatiq peut utiliser plusieurs brokers de messages, dont RabbitMQ et Redis. + +--- + +## TaskTiger + +[TaskTiger](https://tasktiger.readthedocs.io/en/latest/) est une autre bibliothèque Python pour la gestion des tâches. Elle utilise également Redis comme système de file d'attente. TaskTiger offre des fonctionnalités uniques telles que la possibilité de gérer les tâches en batch et une interface d'administration intégrée. + +--- + +## Apache Airflow + +[Airflow](https://airflow.apache.org/) est une plateforme utilisée pour programmer et surveiller des flux de travail. Créée par Airbnb, elle est utilisée pour gérer les processus ETL complexes. Bien qu'elle ne soit pas une bibliothèque Python à proprement parler, elle est écrite en Python et est couramment utilisée dans les projets de science des données. + +--- + +Ces bibliothèques offrent chacune une approche unique de la mise en file d'attente des tâches en Python et sont toutes de bonnes alternatives à Celery selon les besoins spécifiques de votre projet. + +--- + +# Utilisation de RQ (Redis Queue) + +--- + +## Installation + +Pour commencer à utiliser RQ, vous devez l'installer en exécutant la commande suivante : + +```{.bash .numberLines} +pip install rq +``` + +--- + +## Configuration + +RQ nécessite un broker de messages pour fonctionner, et il utilise Redis par défaut. Assurez-vous d'avoir installé et démarré un serveur Redis sur votre machine. Une fois le serveur Redis en place, vous pouvez créer une connexion à Redis dans votre script Python : + +```{.python .numberLines} +from redis import Redis +from rq import Queue + +# Se connecter à Redis +redis_conn = Redis() + +# Créer une file d'attente +q = Queue(connection=redis_conn) +``` + +--- + +## Envoi de tâches + +Une fois que vous avez configuré votre file d'attente, vous pouvez commencer à y ajouter des tâches. Voici un exemple de fonction que nous pourrions vouloir exécuter en arrière-plan : + +```{.python .numberLines} +def say_hello(name): + print(f'Hello, {name}!') +``` + +Pour ajouter cette tâche à notre file d'attente, nous utilisons la méthode `enqueue()` de notre objet `Queue` : + +```{.python .numberLines} +q.enqueue(say_hello, 'Alice') +``` + +Cette tâche sera alors ajoutée à la file d'attente et exécutée par un "travailleur" RQ dès qu'il sera disponible. + +--- + +## Démarrage d'un Worker + +Pour démarrer un travailleur RQ qui va consommer des tâches depuis la file d'attente, vous pouvez utiliser la commande `rq worker` dans votre terminal. Assurez-vous d'être dans le même environnement que celui où votre serveur Redis est en cours d'exécution : + +```{.bash .numberLines} +rq worker +``` + +Le travailleur va démarrer et commencer à traiter les tâches de la file d'attente. + +--- + +L'utilisation de RQ est un moyen simple et efficace de gérer les tâches en arrière-plan en Python. Avec sa simplicité et sa facilité d'utilisation, RQ est un excellent choix pour les applications Python qui nécessitent la mise en file d'attente de tâches. diff --git a/documentation/99-datasets.md b/documentation/99-datasets.md new file mode 100644 index 0000000..160d614 --- /dev/null +++ b/documentation/99-datasets.md @@ -0,0 +1,4 @@ +- https://www.data.gouv.fr/fr/datasets/ +- https://www.data.gouv.fr/fr/datasets/geolocalisation-des-etablissements-du-repertoire-sirene-pour-les-etudes-statistiques/ +- https://www.data.gouv.fr/fr/datasets/fichier-des-prenoms-edition-2016-voir-fichier-des-prenoms-de-1900-a-2019/ +- diff --git a/documentation/99-workshop-pandas-shoes.md b/documentation/99-workshop-pandas-shoes.md new file mode 100644 index 0000000..7625152 --- /dev/null +++ b/documentation/99-workshop-pandas-shoes.md @@ -0,0 +1,41 @@ +# Exercice de traitement de données avec Pandas + +Tiré du site de Guillaume Dueymes : https://www.guillaumedueymes.com/courses/formation_python/8-pandas-exercice/ + +Nous allons analyser un data set contenant des informations sur 10 000 paires de chaussures vendues sur le site Amazon, +avec de nombreuse caractéristiques comme le prix minimal, le prix maximal, les couleurs disponibles, les tailles disponibles, leurs poids, la marque… +Le fichier est fourni compressé sous le nom `womens-shoes.csv.xz` (511 ko compressé, 65,1 Mo décompressé) + +--- + +## Découverte du data set + +1. À l’aide de la fonction `pandas.read_csv()`, importez entièrement le data set et enregistrez-le dans une variable `shoes`. +2. Utilisez la méthode `.head()` pour afficher les premières lignes du `DataFrame`. +3. Il y a plus de 4 colonnes, beaucoup ne sont pas visibles. Afin de toutes les voir lors de l'affichage, utilisez la fonction `pandas.set_option()` pour que `.head()` affiche toutes les colonnes du `DataFrame`. (consultez la documentation de `set_option()`) +4. On va garder uniquement les colonnes intéressantes. Grâce à la syntaxe de filtrage par colonnes, créez une variable `shoes_light`, comprenant uniquement les colonnes suivantes : `id`, `name`, `dateUpdated`, `colors`, `prices.amountMax`, `prices.amountMin` et `prices.merchant`. Affichez le `head()` de `shoes_light`. + +--- + +## Data Cleaning + +1. À l'aide de l'attribut `.dtypes` du dataframe, regardez attentivement les types de chaque colonne. Certaines ont un type qui n'est pas celui attendu. Lesquelles ? +2. À l'aide des méthodes `.isnull()` (ou `.isna()`), `.sum()` et `len()`, calculez pour chaque colonne le pourcentage de valeurs non renseignées. Notez quelque part celles qui ont un non remplissage supérieur à 10%. +3. Supprimez du dataframe `shoes_light` les colonnes que vous avez notées dans la question précédente, elles ont trop de valeurs non renseignées. +4. À l'aide de la méthode `.to_datetime()` du dataframe, convertissez le type de la colonne `dateUpdated`. + +--- + +## Features Modeling + +1. Ajoutez au dataframe une nouvelle colonne `prices.amountAverage` calculant la moyenne des colonnes `prices.amountMax` et `prices.amountMin` (via une addition et une division par 2). +2. Grâce à l'attribut `Series.dt.weekday`, ajoutez au dataframe une nouvelle colonne `dayOfweekUpdated`, extrayant depuis la colonne `dateUpdated` le jour de la semaine où les produits sont mis à jour (un nombre entre 0 et 6). + +--- + +## Data Analyse + +1. Affichez le prix moyen, écart type, etc. des chaussures avec la méthode `.describe()`. +2. Y a-t-il de grandes différences de prix en fonction de la marque ? À l'aide des méthodes `groupby()`, `mean()` et `sort_values()`, créez une variable `luxury` contenant les 10 marques les plus chères, puis une variable `low_cost` contenant les 10 marques les moins chères. +3. Grâce à la méthode `value_counts()`, déterminez le jour de la semaine où les produits sont le plus souvent mis à jour. +4. **(Optionnel)** Donnez le prix moyen des produits de la marque `easy street` mis à jour un jeudi (jour 3). diff --git a/documentation/assets/diagrams/.$structures-dataframe-demonstration.drawio.bkp b/documentation/assets/diagrams/.$structures-dataframe-demonstration.drawio.bkp new file mode 100644 index 0000000..5cc1cb4 --- /dev/null +++ b/documentation/assets/diagrams/.$structures-dataframe-demonstration.drawio.bkp @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/documentation/assets/diagrams/.$structures-dataframe-groupby.drawio.bkp b/documentation/assets/diagrams/.$structures-dataframe-groupby.drawio.bkp new file mode 100644 index 0000000..1733728 --- /dev/null +++ b/documentation/assets/diagrams/.$structures-dataframe-groupby.drawio.bkp @@ -0,0 +1,534 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/documentation/assets/diagrams/.$structures-dataframe-merge-1.drawio.bkp b/documentation/assets/diagrams/.$structures-dataframe-merge-1.drawio.bkp new file mode 100644 index 0000000..fe5ddaa --- /dev/null +++ b/documentation/assets/diagrams/.$structures-dataframe-merge-1.drawio.bkp @@ -0,0 +1,453 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/documentation/assets/diagrams/.$structures-dataframe-shape.drawio.bkp b/documentation/assets/diagrams/.$structures-dataframe-shape.drawio.bkp new file mode 100644 index 0000000..10d906a --- /dev/null +++ b/documentation/assets/diagrams/.$structures-dataframe-shape.drawio.bkp @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/documentation/assets/diagrams/.$structures-dataframe-windowing.drawio.bkp b/documentation/assets/diagrams/.$structures-dataframe-windowing.drawio.bkp new file mode 100644 index 0000000..0dba99c --- /dev/null +++ b/documentation/assets/diagrams/.$structures-dataframe-windowing.drawio.bkp @@ -0,0 +1,193 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/documentation/assets/diagrams/.$structures-series-demonstration.drawio.bkp b/documentation/assets/diagrams/.$structures-series-demonstration.drawio.bkp new file mode 100644 index 0000000..3ad34a7 --- /dev/null +++ b/documentation/assets/diagrams/.$structures-series-demonstration.drawio.bkp @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/documentation/assets/diagrams/structures-dataframe-demonstration.drawio b/documentation/assets/diagrams/structures-dataframe-demonstration.drawio new file mode 100644 index 0000000..bc9d000 --- /dev/null +++ b/documentation/assets/diagrams/structures-dataframe-demonstration.drawio @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/documentation/assets/diagrams/structures-dataframe-groupby.drawio b/documentation/assets/diagrams/structures-dataframe-groupby.drawio new file mode 100644 index 0000000..8225949 --- /dev/null +++ b/documentation/assets/diagrams/structures-dataframe-groupby.drawio @@ -0,0 +1,534 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/documentation/assets/diagrams/structures-dataframe-merge-1.drawio b/documentation/assets/diagrams/structures-dataframe-merge-1.drawio new file mode 100644 index 0000000..6a7d5ed --- /dev/null +++ b/documentation/assets/diagrams/structures-dataframe-merge-1.drawio @@ -0,0 +1,476 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/documentation/assets/diagrams/structures-dataframe-shape.drawio b/documentation/assets/diagrams/structures-dataframe-shape.drawio new file mode 100644 index 0000000..e6c0a80 --- /dev/null +++ b/documentation/assets/diagrams/structures-dataframe-shape.drawio @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/documentation/assets/diagrams/structures-dataframe-shape.png b/documentation/assets/diagrams/structures-dataframe-shape.png new file mode 100644 index 0000000..f78b145 Binary files /dev/null and b/documentation/assets/diagrams/structures-dataframe-shape.png differ diff --git a/documentation/assets/diagrams/structures-dataframe-windowing.drawio b/documentation/assets/diagrams/structures-dataframe-windowing.drawio new file mode 100644 index 0000000..a167724 --- /dev/null +++ b/documentation/assets/diagrams/structures-dataframe-windowing.drawio @@ -0,0 +1,193 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/documentation/assets/diagrams/structures-series-demonstration.drawio b/documentation/assets/diagrams/structures-series-demonstration.drawio new file mode 100644 index 0000000..2c0a31d --- /dev/null +++ b/documentation/assets/diagrams/structures-series-demonstration.drawio @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/documentation/assets/diagrams/structures-series-index.drawio b/documentation/assets/diagrams/structures-series-index.drawio new file mode 100644 index 0000000..26157c6 --- /dev/null +++ b/documentation/assets/diagrams/structures-series-index.drawio @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/documentation/assets/images/charts-box-outliers.png b/documentation/assets/images/charts-box-outliers.png new file mode 100644 index 0000000..933f716 Binary files /dev/null and b/documentation/assets/images/charts-box-outliers.png differ diff --git a/documentation/assets/images/charts-candlebox-plotly.png b/documentation/assets/images/charts-candlebox-plotly.png new file mode 100644 index 0000000..86f9dab Binary files /dev/null and b/documentation/assets/images/charts-candlebox-plotly.png differ diff --git a/documentation/assets/images/eda-matplotlib-bar-default.png b/documentation/assets/images/eda-matplotlib-bar-default.png new file mode 100644 index 0000000..440843e Binary files /dev/null and b/documentation/assets/images/eda-matplotlib-bar-default.png differ diff --git a/documentation/assets/images/eda-matplotlib-bar-labeled.png b/documentation/assets/images/eda-matplotlib-bar-labeled.png new file mode 100644 index 0000000..3a14b6d Binary files /dev/null and b/documentation/assets/images/eda-matplotlib-bar-labeled.png differ diff --git a/documentation/assets/images/eda-matplotlib-bar-themed.png b/documentation/assets/images/eda-matplotlib-bar-themed.png new file mode 100644 index 0000000..daecf3b Binary files /dev/null and b/documentation/assets/images/eda-matplotlib-bar-themed.png differ diff --git a/documentation/assets/images/eda-matplotlib-pie-themed.png b/documentation/assets/images/eda-matplotlib-pie-themed.png new file mode 100644 index 0000000..e6ca379 Binary files /dev/null and b/documentation/assets/images/eda-matplotlib-pie-themed.png differ diff --git a/documentation/assets/images/eda-plotly-compose-2axes.png b/documentation/assets/images/eda-plotly-compose-2axes.png new file mode 100644 index 0000000..c3a73cd Binary files /dev/null and b/documentation/assets/images/eda-plotly-compose-2axes.png differ diff --git a/documentation/assets/images/eda-plotly-compose-pie-label.png b/documentation/assets/images/eda-plotly-compose-pie-label.png new file mode 100644 index 0000000..4058fc0 Binary files /dev/null and b/documentation/assets/images/eda-plotly-compose-pie-label.png differ diff --git a/documentation/assets/images/eda-plotly-express-bar-custom.png b/documentation/assets/images/eda-plotly-express-bar-custom.png new file mode 100644 index 0000000..ca1856c Binary files /dev/null and b/documentation/assets/images/eda-plotly-express-bar-custom.png differ diff --git a/documentation/assets/images/eda-plotly-express-bar-default.png b/documentation/assets/images/eda-plotly-express-bar-default.png new file mode 100644 index 0000000..cbd8784 Binary files /dev/null and b/documentation/assets/images/eda-plotly-express-bar-default.png differ diff --git a/documentation/assets/images/eda-plotly-express-bar-gradient.png b/documentation/assets/images/eda-plotly-express-bar-gradient.png new file mode 100644 index 0000000..1f2dac7 Binary files /dev/null and b/documentation/assets/images/eda-plotly-express-bar-gradient.png differ diff --git a/documentation/assets/images/eda-plotly-express-bar-themed.png b/documentation/assets/images/eda-plotly-express-bar-themed.png new file mode 100644 index 0000000..4553420 Binary files /dev/null and b/documentation/assets/images/eda-plotly-express-bar-themed.png differ diff --git a/documentation/assets/images/eda-plotly-express-pie-colors.png b/documentation/assets/images/eda-plotly-express-pie-colors.png new file mode 100644 index 0000000..5fdd05d Binary files /dev/null and b/documentation/assets/images/eda-plotly-express-pie-colors.png differ diff --git a/documentation/assets/images/eda-plotly-express-scatter-themed.png b/documentation/assets/images/eda-plotly-express-scatter-themed.png new file mode 100644 index 0000000..6c2fa88 Binary files /dev/null and b/documentation/assets/images/eda-plotly-express-scatter-themed.png differ diff --git a/documentation/assets/images/eda-plotly-express-sunburst-themed.png b/documentation/assets/images/eda-plotly-express-sunburst-themed.png new file mode 100644 index 0000000..4a0acd4 Binary files /dev/null and b/documentation/assets/images/eda-plotly-express-sunburst-themed.png differ diff --git a/documentation/assets/images/eda-plotly-logo.png b/documentation/assets/images/eda-plotly-logo.png new file mode 100644 index 0000000..766a263 Binary files /dev/null and b/documentation/assets/images/eda-plotly-logo.png differ diff --git a/documentation/assets/images/jupyter-cell-display.png b/documentation/assets/images/jupyter-cell-display.png new file mode 100644 index 0000000..8e0c6a1 Binary files /dev/null and b/documentation/assets/images/jupyter-cell-display.png differ diff --git a/documentation/assets/images/jupyter-cell-types.png b/documentation/assets/images/jupyter-cell-types.png new file mode 100644 index 0000000..9e0548e Binary files /dev/null and b/documentation/assets/images/jupyter-cell-types.png differ diff --git a/documentation/assets/images/jupyter.png b/documentation/assets/images/jupyter.png new file mode 100644 index 0000000..4010b9b Binary files /dev/null and b/documentation/assets/images/jupyter.png differ diff --git a/documentation/assets/images/logo-pandas.png b/documentation/assets/images/logo-pandas.png new file mode 100644 index 0000000..1a6da9d Binary files /dev/null and b/documentation/assets/images/logo-pandas.png differ diff --git a/documentation/assets/images/plotly-chart-bar.png b/documentation/assets/images/plotly-chart-bar.png new file mode 100644 index 0000000..cc5ebe4 Binary files /dev/null and b/documentation/assets/images/plotly-chart-bar.png differ diff --git a/documentation/assets/images/rest-api-general.png b/documentation/assets/images/rest-api-general.png new file mode 100644 index 0000000..1cfd018 Binary files /dev/null and b/documentation/assets/images/rest-api-general.png differ diff --git a/documentation/assets/images/scikit-library-logo.png b/documentation/assets/images/scikit-library-logo.png new file mode 100644 index 0000000..32f1579 Binary files /dev/null and b/documentation/assets/images/scikit-library-logo.png differ diff --git a/documentation/assets/images/structures-dataframe-demonstration.drawio.png b/documentation/assets/images/structures-dataframe-demonstration.drawio.png new file mode 100644 index 0000000..1ade9a7 Binary files /dev/null and b/documentation/assets/images/structures-dataframe-demonstration.drawio.png differ diff --git a/documentation/assets/images/structures-dataframe-groupby-groups-country.png b/documentation/assets/images/structures-dataframe-groupby-groups-country.png new file mode 100644 index 0000000..1cf7ac8 Binary files /dev/null and b/documentation/assets/images/structures-dataframe-groupby-groups-country.png differ diff --git a/documentation/assets/images/structures-dataframe-groupby-table.png b/documentation/assets/images/structures-dataframe-groupby-table.png new file mode 100644 index 0000000..e221ec3 Binary files /dev/null and b/documentation/assets/images/structures-dataframe-groupby-table.png differ diff --git a/documentation/assets/images/structures-dataframe-merge-1.png b/documentation/assets/images/structures-dataframe-merge-1.png new file mode 100644 index 0000000..65a7b69 Binary files /dev/null and b/documentation/assets/images/structures-dataframe-merge-1.png differ diff --git a/documentation/assets/images/structures-dataframe-merge-inner.png b/documentation/assets/images/structures-dataframe-merge-inner.png new file mode 100644 index 0000000..d034969 Binary files /dev/null and b/documentation/assets/images/structures-dataframe-merge-inner.png differ diff --git a/documentation/assets/images/structures-dataframe-shape.png b/documentation/assets/images/structures-dataframe-shape.png new file mode 100644 index 0000000..796e699 Binary files /dev/null and b/documentation/assets/images/structures-dataframe-shape.png differ diff --git a/documentation/assets/images/structures-dataframe-windowing-data.png b/documentation/assets/images/structures-dataframe-windowing-data.png new file mode 100644 index 0000000..df6a77d Binary files /dev/null and b/documentation/assets/images/structures-dataframe-windowing-data.png differ diff --git a/documentation/assets/images/structures-series-index.png b/documentation/assets/images/structures-series-index.png new file mode 100644 index 0000000..3845293 Binary files /dev/null and b/documentation/assets/images/structures-series-index.png differ diff --git a/documentation/assets/images/structures-series-intro.png b/documentation/assets/images/structures-series-intro.png new file mode 100644 index 0000000..717b034 Binary files /dev/null and b/documentation/assets/images/structures-series-intro.png differ diff --git a/documentation/assets/images/structures-series-shape.png b/documentation/assets/images/structures-series-shape.png new file mode 100644 index 0000000..120f21f Binary files /dev/null and b/documentation/assets/images/structures-series-shape.png differ diff --git a/documentation/new-00.0-chapter-subtitle.md b/documentation/new-00.0-chapter-subtitle.md new file mode 100644 index 0000000..b210377 --- /dev/null +++ b/documentation/new-00.0-chapter-subtitle.md @@ -0,0 +1,4 @@ +--- +title: Pandas +author: Steve Kossouho +--- diff --git a/documentation/new-01.1-pandas-concepts.md b/documentation/new-01.1-pandas-concepts.md new file mode 100644 index 0000000..8103c62 --- /dev/null +++ b/documentation/new-01.1-pandas-concepts.md @@ -0,0 +1,134 @@ +--- +title: Pandas +author: Steve Kossouho +--- + +# Concepts de base de Pandas + +---- + +## La bibliothèque Pandas + +Il existe d'assez nombreuses bibliothèques Python destinées à faciliter le traitement de données. +Bien que la bascule prenne du temps, de plus en plus de développeurs et d'entreprises optent pour +inclure ces bibliothèques dans leur flux de travail, bien souvent en remplacement de solutions +payantes telles que **SAS**, **Stata** ou **MatLab**. + +Parmi ces bibliothèques, on compte notamment `pyspark` (`Apache Spark`), `pandas`, `polars` +ou encore `dask`. + +`pandas`, la plus connue d'entre elles, est une bibliothèque assez intuitive, destinée à des +développeurs, et pensée pour des développeurs qui sont déjà habitués à traiter de la donnée +dans des logiciels propriétaires. + +![_Logo de Pandas_](assets/images/logo-pandas.png){width=96px} + +---- + +### Installer `pandas` + +Pour bénéficier de la meilleure expérience avec Pandas, vous pouvez l'installer avec l'outil `pip` : + +```bash {.numberLines} +# Pandas-stubs offre l'autocomplétion sur des éléments dynamiques +# Openpyxl permet à pandas de charger des documents Excel 2003- et 2007+ +pip install pandas pandas-stubs openpyxl +``` + +---- + +## Types de base de Pandas + +Toute l'utilisation de la bibliothèque `Pandas` tourne autour de 3 classes principales permettant de manipuler des données en 1D ou en 2D. + +- `Series` : Séries de données (1 dimension) +- `DataFrame` : Tableaux de données (2 dimensions) +- `Index` : Séries de données identifiant des colonnes ou lignes + +[Guide utilisateur Pandas] + +---- + +### Les objets `Series` + +La classe `Series` est la plus simple à manipuler. Elle stocke et permet de gérer les données d'un tableau à une dimension. + +```python {.numberLines} +import pandas as pd + +series = pd.Series([0, 1, 1, 2, 3, 5, 8, 13, 21]) +``` + +[pandas.Series] + +---- + +### Les objets `DataFrame` + +La classe `DataFrame` permet de stocker et de manipuler des données dans un tableau à deux dimensions. Cette classe est séparée de la classe `Series`, au moins parce que les objets `DataFrame` possèdent deux index, au lieu d'un seul pour une `Series` (nous allons le découvrir plus tard). + +```python {.numberLines} +import pandas as pd + +dataframe = pd.DataFrame(data=[["Paul", 1974], ["Quentin", 1991], ["Aude", 1987]]) +``` + +[pandas.DataFrame] + +---- + +### Les objets `Index` + +La classe `Index` est similaire à un objet de la classe `Series`. C'est une séquence de valeurs, qui est utilisée par Pandas pour étiqueter des colonnes d'un `DataFrame`, ou des enregistrements d'une `Series` ou d'un `DataFrame`. Tout objet `Series` ou `DataFrame` possède toujours un index par axe. + +```python {.numberLines} +import pandas as pd + +index = pd.Index(data=[1, 2, 3]) +``` + +[pandas.Index] + +---- + +### Alternatif : Les objets `MultiIndex` + +Un objet `MultiIndex` est un `Index` dont les étiquettes sont composées sur plusieurs niveaux. Cela permet d'extraire des lignes ou des colonnes hiérarchiquement. + +```python {.numberLines} +import pandas as pd + +index = pd.MultiIndex.from_tuples([("FR", "Paris"), ("FR", "Pau"), ("ES", "Madrid"), ("ES", "Sevilla")]) +series = pd.Series(data=[1, 2, 3, 4], index=index) +print(series.loc["FR"]) +``` + +[Indexation avancée] + +---- + +### Alternatif : Les objets `DatetimeIndex` + +Un objet `DatetimeIndex` est un `Index` contenant des objets de type date, et dont le contenu est générable de façon déterministe. Ils contiennent généralement des dates à intervalles réguliers, ou des dates suivant des règles simples (ex. tous les 4 mercredi, toutes les fins de mois etc.) + +[Indexation avancée] + + +---- + +### Structure d'une `Series` + +![Series](assets%2Fimages%2Fstructures-series-shape.png) + +---- + +### Structure d'un `DataFrame` + +![DataFrame](assets%2Fimages%2Fstructures-dataframe-shape.png) + +[Guide utilisateur Pandas]: https://pandas.pydata.org/docs/user_guide/index.html#user-guide Pandas user guide +[pandas.Series]: https://pandas.pydata.org/docs/reference/series.html +[pandas.DataFrame]: https://pandas.pydata.org/docs/reference/frame.html +[pandas.Index]: https://pandas.pydata.org/docs/reference/indexing.html +[pandas.MultiIndex]: https://pandas.pydata.org/docs/user_guide/advanced.html +[Indexation avancée]: https://pandas.pydata.org/docs/user_guide/advanced.html diff --git a/documentation/new-01.2-jupyter.md b/documentation/new-01.2-jupyter.md new file mode 100644 index 0000000..8d3d921 --- /dev/null +++ b/documentation/new-01.2-jupyter.md @@ -0,0 +1,152 @@ +--- +title: Jupyter +author: Steve Kossouho +--- + +# Jupyter + +---- + +## Qu'est-ce que Jupyter ? + +Jupyter est un serveur web proposant une interface de console interactive Python avancée. Le projet trouve ses origines dans le projet `ipython`, qui est une console interactive avancée pour terminal. + +`ipython` a été créé par Fernando Pérez en 2001. L'outil interactif web associé, `ipython` notebook, a été créé fin 2011. Ce n'est qu'en 2014 que ce projet a été extrait du projet `IPython` pour devenir Jupyter. + +Jupyter est très utilisé par des professionnels pratiquant l'analyse de données, car il est pratique via son interface d'itérer et de tester des choses simples. + +---- + +![Carnet de notes Jupyter](assets%2Fimages%2Fjupyter.png) + +---- + +## Avantages de Jupyter pour l'analyse de données + +Grâce à son interface présentée en _cahier modulaire_, il est très pratique de rédiger des bouts de code plus ou moins indépendants les uns des autres. Il est même possible de rédiger des blocs de texte pour annoter des carnets Jupyter (au format Markdown). + +L'avantage visuel de Jupyter par rapport à l'exécution de scripts complets est que la sortie d'exécution d'une cellule de code apparaît directement dans l'interface, sous la zone de code exécutée. + +Les professionnels voulant réaliser des scripts simples pour traiter des données d'entrée auront ainsi la facilité de vérifier si une portion de leur code pose problème. + +---- + +## Installation et démarrage de Jupyter + +Pour installer et lancer Jupyter sur sa propre machine, il faut installer un paquet avec `pip` : + +```bash {.numberLines} +pip install jupyter +``` + +Une solution plus avancée que Jupyter peut être alternativement installée; elle propose des fonctionnalités +supplémentaires, ainsi qu'une interface plus avancée et plus proche de celle d'un IDE comme PyCharm : + +```bash {.numberLines} +pip install jupyterlab +``` + +---- + +### Démarrer Jupyter ou Jupyterlab + +Une fois la bibliothèque de votre choix installée, vous pouvez démarrer un serveur de carnets via une des commandes suivantes : + +```bash {.numberLines} +jupyter notebook # nécessite d'installer jupyter +``` + +```bash {.numberLines} +jupyter-lab # nécessite d'installer jupyterlab +``` + +---- + +## Interface de Jupyter, cellules et kernel + +Jupyter vous permet de créer facilement, dans un carnet, des zones indépendantes, dans lesquelles vous pouvez saisir du code, +ou bien du texte au format [Markdown]. + +![Types de cellules Jupyter](assets%2Fimages%2Fjupyter-cell-types.png) + +---- + +### Kernel + +Un Kernel Jupyter est un environnement d'exécution avec lequel Jupyter est capable de communiquer, pour exécuter des bouts de code entre autres. + +Le Kernel le plus connu est fourni par la bibliothèque `ipython`; il communique avec l'application Jupyter via un protocole réseau +fourni par une application nommée ZeroMQ, et fournit une console interactive Python. Les variables qui sont créées pendant son exécution restent disponibles pendant toute la durée de vie du kernel. + +---- + +## Raccourcis clavier utiles de Jupyter + +Il existe deux endroits pour envoyer des raccourcis clavier à Jupyter : + +- L'édition d'une cellule (presser `[Entrée]`) +- L'interface générale (presser `[ESC]`) + +---- + +### Lors de l'édition + +- `Tab` : autocomplétion ou indentation +- `Shift + Tab` : infobulle de documentation +- `Ctrl + ]` : Indentation de la ligne +- `Ctrl + [` : Désindentation de la ligne +- `Ctrl + Shift + P` : Palette de commandes +- `Alt + ↑/↓` : Déplacer la ligne courante + +---- + +### Dans l'interface générale + +- `↑/↓` : Sélectionner une cellule +- `Shift + ↑/↓` : Augmenter ou réduire la sélection de cellules +- `D + D` : Supprimer les cellules sélectionnées +- `Y` : Définir la cellule comme étant du code +- `M` : Définir la cellule comme étant du Markdown + +---- + +### Toujours disponibles + +- `Shift + Entrée` : Exécuter la cellule, passer à la suivante +- `Ctrl + Entrée` : Exécuter les cellules sélectionnées +- `Alt + Entrée` : Exécuter la cellule, ajouter une nouvelle en dessous + +[Markdown]: https://docs.framasoft.org/fr/grav/markdown.html + +---- + +## Fonctionnement des cellules de code + +Lorsque vous exécutez un Kernel _Jupyter_, vous êtes dans une console interactive Python +dans laquelle chaque exécution peut changer l'état général (variables créées, mises à jour). + +Quand vous exécutez une cellule, _Jupyter_ imite la console interactive Python en affichant +automatiquement la valeur de la dernière instruction d'une cellule. + +Pour certains types (ex. `pandas.DataFrame`{.python} ou `matplotlib.axes.Axes`{.python}), le +rendu peut être augmenté en affichant par exemple un tableau HTML, ou directement +le contenu d'un graphique. + +---- + +![Rendu avec `display`](assets/images/jupyter-cell-display.png) + +---- + +### Affichage adaptatif des contenus + +Vous voudrez occasionnellement pouvoir afficher de façon augmentée plusieurs contenus dans la +même cellule, ce qui n'est pas possible de base puisque seule la dernière instruction +provoque un affichage. + +Jupyter propose en réalité une fonction anormalement confidentielle, `display()`{.python}, +qui affiche les objets de façon améliorée lorsque c'est possible. + +---- + +![Rendu avec `display`](assets/images/jupyter-cell-display.png) diff --git a/documentation/new-01.3-numpy-data.md b/documentation/new-01.3-numpy-data.md new file mode 100644 index 0000000..122bc25 --- /dev/null +++ b/documentation/new-01.3-numpy-data.md @@ -0,0 +1,208 @@ +--- +title: Données avec Numpy +author: Steve Kossouho +--- + +# Création de données avec Numpy + +---- + +## Tableaux Numpy + +Numpy est une bibliothèque Python dont la principale fonctionnalité est codée dans le langage C. Il s'agit d'une structure de données nommée un `ndarray`, à savoir un tableau à **n-dimensions**. + +Le stockage des données dans un tel tableau est beaucoup plus efficace que les listes Python, car les données d'un `ndarray` sont généralement des valeurs dont le type est l'un des types de base gérés nativement par un processeur moderne et stockables efficacement en mémoire (ex. entier 32 bits, entier 64 bits ou flottant 64 bits). + +En plus d'être efficaces, ces tableaux se manipulent en utilisant la syntaxe standard de Python pour les listes, ce qui les rend très pratiques. + +---- + +## Création d'un tableau Numpy + +En règle générale, il est important de savoir générer un tableau avec Numpy, car cela peut occasionnellement servir pour certains calculs. + +Pour créer un tableau à une dimension (l'équivalent d'une liste), c'est extrêmement simple : + +```python {.numberLines} +import numpy as np + +array1 = np.array([1, 2, 3]) # Crée un tableau 1D à partir d'une liste +print(array1) +``` + +Pour créer un tableau à deux dimensions, il suffit d'envoyer une liste contenant des éléments qui sont des listes (un pour chaque ligne) : + +```python {.numberLines} +import numpy as np + +array2 = np.array([[1, 2, 3], [4, 5, 6]]) # Crée un tableau 2D (matrice) +``` + +---- + +## Générer des tableaux avec Numpy + +Nous avons vu qu'il est simple de créer manuellement des tableaux ou matrices depuis des listes Python, mais souvent, +il est intéressant de créer des données qui ont certaines propriétés, comme par exemple : + +- Remplir une matrice de taille x avec des valeurs fixes +- Créer une matrice d'identité (carrée avec 1.0 en diagonale) +- Remplir une matrice de nombres aléatoires +- Remplir une matrice avec une suite arithmétique ou géométrique + +---- + +### Séquence de nombres + +Générer une suite arithmétique de nombres comme la fonction `range()`{.python} est possible grâce à la fonction `numpy.arange()`{.python} : + +```python {.numberLines} +import numpy as np + +range1 = np.arange(0, 10, 1) # De 0 à 9 +range2 = np.arange(0, 10, 2) # De 2 en 2 de 0 à 8 +range3 = np.arange(10, 0, -2) # De -2 en -2 de 10 à 2 +``` + +---- + +### Séquence de valeurs équidistantes + +On peut générer une suite de nombres uniformément disposés entre 2 valeurs incluses, +via la fonction "espace linéaire" `numpy.linspace()`{.python} : + +```python {.numberLines} +import numpy as np + +linspace1 = np.linspace(0, 10, num=5) # 0, 2.5, 5.0, 7.5 et 10.0 +``` + +---- + +### Tableaux de nombres aléatoires + +Vous pouvez générer des séquences ou matrices de nombres aléatoires avec plusieurs fonctions fournies par le module `numpy.random` : + +```python {.numberLines} +import numpy as np + +# Loi Gaussienne (normale), 3x3, centre à 5.0 et écart-type de 5.0 +normal1 = np.random.normal(loc=5.0, scale=5.0, size=(3, 3)) +# Loi de Pareto +pareto1 = np.random.pareto(10.0, size=100) +# Nombres pseudo-aléatoires entre 0 et 1 non inclus +basic1 = np.random.random(size=10) +``` + +---- + +### Tableaux préinitialisés + +Vous pouvez très aisément générer des tableaux numpy contenant, au choix, des __zéros__, des __un__, une valeur constante +ou encore des matrices diagonales (identité notamment); + +```python {.numberLines} +import numpy as np +from math import pi + +# Tableau de 5 x 5 rempli de zéros +zeros = np.zeros((5, 5)) +# Tableau de 9 x 4 rempli de 1 +ones = np.ones((9, 4)) +# Tableau de 6 x 2 rempli de Pi +pis = np.full((6, 2), pi) +# Matrice d'identité (carrée 3x3) +identity = np.identity(3) +``` + +---- + +### Générer des séquences de dates avec Pandas + +Pandas propose de créer des __index__ d'objets date, dont le type est nommé `datetime64[ns]` (précision à la nanoseconde). +La fonction disponible pour ce faire est nommée `pandas.date_range`{.python} et s'utilise de deux façons : + +```python {.numberLines} +import pandas as pd + +# Générer un index contenant 4 dates équidistantes +# Les 4 dates contiennent celle de départ et celle de fin +date_index = pd.date_range("2024-01-01", "2024-12-31", periods=4) +``` + +Cette méthode permet de créer un __index__ de dates à intervalle constant. Il faut pour cela utiliser l'argument +`periods=` et y préciser le nombre de dates à obtenir dans l'index. + +---- + +L'autre utilisation de la fonction `date_range()` consiste à générer un __index__ de dates, +non pas disposées à intervalles réguliers, mais disposées selon un motif; par exemple, il +est possible de générer un __index__ dans lequel on générera tous les lundi entre deux +dates. Pour cela il faudra utiliser l'argument `freq=` : + +```python {.numberLines} +import pandas as pd + +# Entre le 1er janvier et le premier mars inclus, générer tous les lundis +date_index = pd.date_range("2024-01-01", "2024-03-01", freq="W-MON") # weekly-monday +# Entre le 1er janvier et le premier mars inclus, générer un dimanche sur deux +date_index2 = pd.date_range("2024-01-01", "2024-03-01", freq="2W-SUN") # weekly-sunday +``` + +Il est possible de générer de nombreuses variations, qui sont documentées ici : [date_range:freq]. + +---- + +### Routines de création de tableaux + +De très nombreuses fonctions de création de tableaux sont disponibles dans Numpy en sus des fonctions +présentées dans ce support. Elles sont présentées dans le document [Création de tableaux] + +---- + +## Calcul avec les tableaux Numpy + +Un des principaux intérêts des tableaux Numpy, outre leur consommation mémoire, est leur utilisation. +Il est simplement possible d'effectuer des modifications simples sur des tableaux, telles que : + +- Calcul vectorisé +- Produit matriciel +- Agrégations (statistiques, arithmétique etc.) + +---- + +### Opérations arithmétiques Numpy + +Vous pouvez appliquer un calcul arithmétique à tous les éléments d'un tableau Numpy +sans avoir à effectuer de boucle `for`{.python} : + +```python {.numberLines} +import numpy as np + +numbers = np.array([1, 2, 3, 4, 5]) +computed = numbers * 4 + 1.5 # Nouveau tableau où tous les éléments ont été modifiés +``` + +---- + +### Produit matriciel + +Python 3.7 (2018) a introduit un nouvel opérateur destiné au produit matriciel : `@`{.python} + +Cet opérateur peut être personnalisé par n'importe quelle classe via la méthode `__matmul__`{.python}. +La bibliothèque numpy est une des rares à l'utiliser. + +```python {.numberLines} +import numpy as np + +square = np.random.random(size=(3, 3)) +identity = np.identity(3) +noop = square @ identity +print(square) +print(noop) +``` + +---- + +[date_range:freq]: https://pandas.pydata.org/docs/user_guide/timeseries.html#offset-aliases +[Création de tableaux]: https://numpy.org/doc/stable/reference/routines.array-creation.html diff --git a/documentation/new-01.4-pandas-series.md b/documentation/new-01.4-pandas-series.md new file mode 100644 index 0000000..1422c8c --- /dev/null +++ b/documentation/new-01.4-pandas-series.md @@ -0,0 +1,511 @@ +--- +title: Pandas +author: Steve Kossouho +--- + +# Manipuler et comprendre les séries + +---- + +## Propriétés d'une série + +Une `Series` dans Pandas est un objet contenant une séquence de valeurs. La série tout entière possède un type unique (voir les types à la fin du chapitre précédent) et tous les éléments de cette structure sont d'un type cohérent avec celui de la série. _Une série avec des éléments de types trop divers (ou avec des chaînes de caractères) sera de type `object`._ + +Une série peut également posséder un nom. + +![Exemple de série](assets/images/structures-series-intro.png) + +---- + +## Quand obtient-on une série ? + +Un objet de type `Series` s'obtient soit manuellement, soit en extrayant précisément une colonne ou une ligne de données depuis des données tabulaires. + +---- + +## Types de données dans Pandas + +Le stockage de données en mémoire avec Pandas proposera de manipuler des entrées, dans des types spécifiques à Pandas : + +- `object` : données de types divers, dont chaînes de caractères +- `int64/32/16` : nombres entiers (64 bits maximum) +- `Int64/32/16` : nombres entiers (avec prise en charge des nan) +- `float64` : nombres à virgule flottante (64 bits) +- `complex128` : même les nombres complexes (64 et 64 bits) +- `bool` : valeurs booléennes +- `boolean` : valeurs booléennes (avec prise en charge des nan) +- `datetime64[ns]` : représentation d'une date +- `datetime64[ns, UTC]` : représentation d'une date, avec décalage horaire (UTC) +- `timedelta64` : représentation d'un intervalle de temps +- `category`: choix de valeurs textuelles + +Les types `timedelta64` et `category` sont rarement rencontrés. + +---- + +## Type de données d'une `Series` + +Un objet `Series` possède toujours un type appliqué à tous ses éléments. Selon le type associé, des traitements différents sont possibles sur une série. Il est toujours possible de connaître le type d'une série en accédant à son attribut `dtype` (data type) : + +```python {.numberLines} +import pandas as pd + +series = pd.Series(data=[1, 2, 3, 4]) +print(series.dtype) # affiche "int64" car les données sont toutes compatibles +``` + +---- + +## Créer une série + +Nous avons vu que nous pouvons créer simplement des séries, et nous pouvons le faire en passant des informations variées : + +- Des tableaux Numpy à une dimension (voir sous-chapitre précédent) +- Des listes ou des tuples de données Python (`int`{.python}, `float`{.python}, `str`{.python}, `datetime.datetime`{.python}…) +- Des `Series`{.python} sont aussi acceptées + +---- + +```python {.numberLines} +import numpy as np +import pandas as pd + +# Créer une série avec une somme cumulative générée avec Numpy +series1 = pd.Series(data=np.cumsum([1, 2, 3, 4, 5, 6, 7, 8, 9])) +``` + +Exemple de série créée depuis un tableau Numpy (lui-même généré via des données Python). + +---- + +```python {.numberLines} +import pandas as pd + +# Créer une série avec des entiers, passés via des données standard Python +series2 = pd.Series(data=[1, 2, 3, 4, 5, 6, 7, 8, 9]) # liste +series3 = pd.Series(data=(1, 2, 3, 4, 5, 6, 7, 8, 9)) # tuple +series4 = pd.Series(data=series2) # série Pandas (pas très utile) +``` + +Exemple de série créée depuis des listes ou tuples Python, ou encore des `Series`{.python} + +---- + +### Créer une série avec un index + +Un index est un ensemble de valeurs associées à chaque élément d'une série pour l'identifier. +Une série possède **toujours** un index, par défaut des nombres successifs commençant par zéro. +Par exemple, si l'on considère la série suivante : + +![Structure d'une série et de l'index](assets/images/structures-series-index.png) + +---- + +![Série](assets/images/structures-series-index.png) + +Les valeurs d'index `"U1"` et `"U3"`, par exemple, sont associées respectivement aux valeurs `"Alain"` et `"Gilles"` de la série. Il sera également possible d'extraire la valeur `"Alain"` avec le code suivant : + +```python {.numberLines} +import pandas as pd + +s1 = pd.Series(data=["Alain", "Lucie", "Gilles", "André", "Zoé", "Paul"], index=["U1", "U2", "U3", "U4", "U5", "U6"]) +print(s1["U1"]) # Affiche la valeur "Alain" en extrayant depuis l'index "U1" +``` + +---- + +### Créer une série en forçant le type + +Vous pouvez créer une série avec Pandas en précisant le type (`dtype`) à appliquer à ses valeurs; par exemple, +vous pourriez définir votre série en passant uniquement des nombres entiers, et considérer que le type de la série +devrait malgré tout être `float64`. Ou, plus intéressant, vous pouvez par exemple créer une série en passant des chaînes +de caractères représentant des dates, et indiquer que ces chaînes doivent être converties vers le type `datetime64[ns]` +(seul le [format ISO 8601](https://fr.wikipedia.org/wiki/ISO_8601) des dates est interprété) : + +```python {.numberLines} +import pandas as pd + +s1 = pd.Series(data=["2023-01-02", "2024-03-17"]) # Une série de chaînes de caractères +s2 = pd.Series(data=["2023-01-02", "2024-03-17"], dtype="datetime64[ns]") # Une série d'objets de type date +s3 = pd.Series(data=["2023-01-02", "2024-03-17"], dtype="float64") # Une erreur va se produire : la conversion n'a pas de sens +``` + +---- + +## Extraire des informations d'une série + +Un objet de type `Series` dans Pandas possède toujours 3 informations distinctes : + +- Les valeurs stockées dans l'objet (attribut `.values : ndarray`{.python}) +- Le nom de l'objet (attribut `s.name`{.python}, peut être `None`{.python}) +- L'index de l'objet (par défaut une séquence de valeurs numériques, attribut `s.index`) + +**Note** : On peut récupérer les valeurs de la série sous forme de tableau numpy via +la méthode `.to_numpy()`{.python}. + +---- + +## Opérations sur les séries + +Pandas propose d'effectuer des opérations diverses sur des séries, pour obtenir des valeurs scalaires, ou +des séries. Par exemple, vous pouvez appliquer des opérations arithmétiques : + +```python {.numberLines} +import pandas as pd + +s1 = pd.Series(data=[1, 2, 3, 4], index=[1, 2, 3, 4], name="counter") +s2 = s1 * 2 # génère une série avec les valeurs 2, 4, 6, 8 +s3 = s1 * s1 # génère une série avec les valeurs 1, 4, 9, 16 +s4 = s1 + 4 # génère une série avec les valeurs 5, 6, 7, 8 +``` + +---- + +Les séries dans Pandas prennent également en charge l'usage d'opérateurs de comparaison, qui permettent de +récupérer des séries de valeurs booléennes. En voici quelques exemples d'illustration : + +```python {.numberLines} +import pandas as pd + +s1 = pd.Series(data=[1, 2, 3, 4], index=[6, 7, 8, 9], name="counter") +s2 = s1 > 2 # génère une série avec False, False, True, True +s3 = (s1 * 5) != 10 # génère une série avec True, False, True, True +print(4 in s1) # vérifie si la valeur 4 fait partie... de l'index +print(3 in s1.values) # vérifie si la valeur 3 fait partie... des données +print(s1.isin([1, 4])) # Dit pour chaque valeur si elle fait partie des valeurs 1 et 4 +``` + +Nous verrons que les séries contenant des valeurs booléennes pourront être utiles plus tard +afin de filtrer des éléments de séries ou de dataframes. + +---- + +### Modifier les valeurs de séries + +Les séries Pandas ne proposent généralement pas d'outils sous forme de méthodes pour en modifier +le contenu. Pour autant, la méthode la plus simple, disponible en Python sur les objets de type `list`{.python} +ou `dict`{.python}, fonctionnera très bien sur une série : + +```python {.numberLines} +import pandas as pd + +fruit = pd.Series(data=["apple", "watermelon", "grapefruit", "lemon"]) +fruit[0] = "Orange" # Remplacer la valeur à l'index 0 +fruit["total"] = "Cocktail" # Ajouter une valeur à un nouvel index +``` + +---- + +## Valeurs vides + +Si l'on considère un document Excel, par exemple, un document peut contenir des valeurs non renseignées. Les cellules desdites +valeurs apparaissent naturellement vides. Dans Pandas, lorsqu'une série (ou un `DataFrame`) possède des cellules vides, la valeur +qui y est contenue est une valeur spéciale nommée `NaN` (Not a number). + +```python {.numberLines} +import pandas as pd +# Vous pouvez utiliser la valeur dans vos séries. Elle n'est disponible que dans Numpy. +from numpy import nan # NaN non disponible depuis numpy 2.0 + +series_with_holes = pd.Series(data=["apple", "watermelon", nan, "grapefruit", nan]) +``` + +> Notez que le type de cette valeur est `float64`, et que de telles valeurs vont influencer notamment le +`dtype` des séries de nombres entiers, qui prendront ainsi le type `float64`. + +---- + +### Méthodes pour les valeurs vides + +Les séries possèdent plusieurs outils pratiques pour obtenir des informations sur leurs valeurs vides. Une première série de +méthodes vous permet d'obtenir, depuis votre série, une série indiquant si une valeur est vide, ou le contraire. Voyons un +exemple : + +```python {.numberLines} +import pandas as pd +from numpy import nan + +s1 = pd.Series(data=[1, 2, nan, 4, nan, 6, 7]) +s_na1 = s1.isna() # donne une série contenant [False, False, True, False, True, False, False] +s_na2 = s1.isnull() # synonyme : donne exactement le même résultat +s_nn1 = s1.notna() # donne une série contenant [True, True, False, True, False, True, True] +s_nn2 = s1.notnull() # synonyme de notna +has_na = s1.hasnans # renvoie True : des valeurs sont vides +``` + +---- + +#### Retirer les valeurs vides + +Une méthode vous permet de retirer les valeurs vides dans une série. Notez que les valeurs d'index +associées ne sont naturellement pas conservées; l'index associé à la série aura certaines valeurs non contiguës. + +```python {.numberLines} +import pandas as pd +from numpy import nan + +s1 = pd.Series(data=[1, 2, nan, 4, nan, 6, 7]) +sc1 = s1.dropna(inplace=False) # donne une série contenant [1, 2, 4, 6, 7] +``` + +---- + +#### Remplir les valeurs vides + +Vous pouvez également remplacer les valeurs vides d'une série, et ce de plusieurs manières, avec la méthode +`.fillna()`{.python} : + +```python {.numberLines} +import pandas as pd +from numpy import nan + +s1 = pd.Series(data=[1, 2, nan, 4, nan, 6, 7]) +# Remplir les valeurs avec une valeur fixe +sc1 = s1.fillna(-1, inplace=False) # donne une série contenant [1, 2, -1, 4, -1, 6, 7] +# Une série où on fait coincider les index pour remplacer les valeurs vides +sc2 = s1.fillna(pd.Series([9, 8, 7, 6, 5, 4, 3])) # donne [1, 2, 7, 4, 5, 6, 7] +``` + +---- + +#### Connaître le nombre de valeurs non vides + +Si l'on peut récupérer le nombre d'éléments d'une série avec la fonction Python `len()`{.python}, la méthode +`count()` d'une série permettra d'obtenir le nombre de valeurs **renseignées**. Pour obtenir le nombre de valeurs vides, +il faudra ruser un peu : + +```python {.numberLines} +import pandas as pd +from numpy import nan + +s1 = pd.Series(data=[1, 2, nan, 4, nan, 6, 7]) +filled_count = s1.count() # Renvoie 5 +empty_count_a = len(s1) - s1.count() # Renvoie 2 +empty_count_b = s1.isna().sum() # sum() compte `True` comme 1, `False` comme 0. Renvoie 2 +``` + +---- + +### Séries booléennes et existence de valeurs `True` + +En Python, les fonctions `any()`{.python} et `all()`{.python} prennent un itérable en argument +et indiquent respectivement si ledit itérable contient un élément équivalent à `True`{.python}, et +si ledit itérable contient uniquement des éléments équivalents à `True`{.python}. + +Le principe est identique avec les méthodes `.any()`{.python} et `.all()`{.python} des séries de +booléens : + +```python {.numberLines} +import pandas as pd + +s1 = pd.Series([False, True, True, False]) +print(s1.any(), s1.all()) # Affiche True et False +``` + +`any()`{.python} renvoie `True` si au moins une valeur est équivalente à `True`{.python}, et `all()`{.python} renvoie +si toutes les valeurs non vides sont équivalentes à `True`{.python}. + +---- + +## Valeurs uniques + +Cela peut être intéressant de savoir dans une série quelles valeurs distinctes apparaissent, ou même +supprimer des valeurs afin de ne garder que des valeurs uniques d'une série. + +```python {.numberLines} +import pandas as pd + +numbers = pd.Series(data=[1, 4, 7, 3, 4, 8, 2, 4, 7, 5, 6, 8, 9, 1, 1, 3, 2]) +uniques = numbers.unique() # valeurs distinctes, tableau numpy +unique2 = numbers.drop_duplicates(keep="first") # ou "last", ou False pour tout supprimer +duplicates = numbers.duplicated(keep="first") # série de booléens indiquant si une valeur est un doublon +frequencies = numbers.value_counts() # série indiquant pour chaque valeur son nombre d'apparitions +print(frequencies[4]) # Récupérer le nombre d'apparitions de la valeur 4 +print(frequencies.get(4)) # Idem, mais renvoie `None` si la valeur n'apparait pas + +``` + +- [Documentation de `drop_duplicates`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.drop_duplicates.html#pandas.Series.drop_duplicates) +- [Documentation de `value_counts`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.value_counts.html#pandas.Series.value_counts) + +---- + +## Appliquer une fonction aux valeurs d'une série + +Occasionnellement, les outils de traitement et de transformations de séries ne suffisent pas. Vous +pouvez avoir besoin de votre propre algorithme à appliquer sur des valeurs d'une série, ex. vous avez +besoin de simplifier une série de valeurs textuelles, en **retirant les accents** et en mettant les caractères +**en minuscules**. La méthode `apply()`{.python} des séries vous permettra ce genre de traitement : + +```python {.numberLines} +import unidecode # pip install unidecode +import pandas as pd + +def unaccent_lower(text: str): + """Retire les accents et convertit en minuscules.""" + return unidecode.unidecode(text).lower() + +names = pd.Series(data=["Élodie", "Jérémy", "Céleste", "Urünundür"]) +# Appliquer la fonction élément par élément pour obtenir une nouvelle série +simple = names.apply(unaccent_lower, by_row="compat") +``` + +[Documentation de `apply`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.apply.html) + +---- + +Vous pouvez également appliquer une fonction et lui donner connaissance de tous les éléments de +la série, en indiquant la valeur `False` à l'argument `by_row=` de la méthode `apply`. +La fonction devra renvoyer une série : + +```python {.numberLines} +import pandas as pd + +def nullify_below_avg(numbers: pd.Series): + """Met à 0 les nombres inférieurs à la moyenne.""" + average = numbers.mean() + values = [(n if n >= average else 0) for n in numbers] + return pd.Series(data=values) + +values = pd.Series(data=[19, 33, 12, 5.7, 61, 14]) +# Appliquer la fonction élément par élément pour obtenir une nouvelle série +simple = values.apply(nullify_below_avg, by_row=False) +``` + +---- + +## Filtrer les éléments d'une série + +Si récupérer un élément unique d'une série fonctionne comme avec un dictionnaire Python, récupérer une partition +de série est aussi possible, via certaines techniques. Vous pouvez récupérer : + +- Plusieurs éléments par la valeur d'index associée +- Plusieurs éléments par un index numérique +- Slicing, par index ou par index numérique +- Éléments filtrés par série de booléens + +---- + +### Récupérer plusieurs éléments par index + +Récupérer plusieurs éléments consiste à passer en tant que clé un objet de type `list`{.python} (et uniquement ce type). +Chaque valeur de ladite liste représente une valeur d'index à extraire. De préférence, on filtrera avec l'index en utilisant +l'attribut `.loc`{.python} (_line of content_ ou _location_ ?) de la série, et on filtrera par index numérique avec l'attribut `.iloc`{.python} : + +```python {.numberLines} +import pandas as pd + +names = pd.Series(data=["Élodie", "Hubert", "Céleste", "Jacques", "Albéric"], index=["E", "H", "C", "J", "A"]) +names_cha = names.loc[["C", "H", "A"]] # récupère Céleste, Hubert et Albéric +names_chb = names[["C", "H", "A"]] # la même opération peut être effectuée directement sur `names` +names_ch2 = names.iloc[[2, 1, 4]] # même résultat que ci-dessus +``` + +---- + +### Slicing + +Vous pouvez récupérer une partition d'une série avec la syntaxe du slicing (`[start:openstop:step]`{.python}) : + +```python {.numberLines} +import pandas as pd +from numpy import r_ # trucs d'indexation + +names = pd.Series(data=["Élodie", "Hubert", "Céleste", "Jacques", "Albéric"], index=["E", "H", "C", "J", "A"]) +elodie_to_celeste = names.loc["E":"C"] # Attention : l'index de fin est inclus +elodie_to_celeste2 = names.iloc[0:3] # Attention : l'index de fin n'est pas inclus +every_two = names.iloc[::2] # L'argument step fonctionne comme prévu +# Appliquer plusieurs slices : outil fourni par Numpy +multiple_slice = names.iloc[r_[0:2, 3:5]] # Note : ne pas préciser l'index de fin ne fonctionne pas +``` + +---- + +### Filtrage par série de booléens + +```python {.numberLines} +import pandas as pd + +values = pd.Series(data=[19, 33, 12, 5.7, 61, 14]) +gt_eighteen = values > 18.0 # booléens : T, T, F, F, T, F +filtered1 = values.loc[gt_eighteen] # 19, 33, 61 +filtered2 = values.loc[[True, True, False, False, True, False]] # Équivalent +``` + +---- + +### Filtrer sur plusieurs conditions + +Vous pouvez filtrer sur plusieurs conditions, en les assemblant avec des opérateurs `ET` et `OU`. +Par exemple, si je souhaite filtrer une série en gardant les valeurs entre 0 et 50, et qui sont divisibles +par 3, je peux effectuer l'opération logique de la façon suivante : + +```python {.numberLines} +import pandas as pd +import numpy as np + +numbers = pd.Series(data=np.arange(0, 101)).sample(frac=1) +# Récupérer les nombres entre 0 et 30 divisibles par 3 +output = numbers.loc[numbers.between(0, 30, inclusive="both") & (numbers % 3 == 0)] +``` + +Pour des questions d'ordre d'évaluation des opérateurs, vous devrez quasi systématiquement utiliser des parenthèses pour discriminer vos conditions. +Les opérateurs à utiliser pour assembler vos propositions sont les opérateurs logiques binaires : + +- `&`{.python} : ET binaire +- `|`{.python} : OU binaire +- `~`{.python} : NON binaire +- `^`{.python} : OU exclusif binaire + +---- + +### Méthodes par type de séries + +On peut très simplement appliquer des opérateurs à des séries, et obtenir des séries. Rappel : + +```python {.numberLines} +import pandas as pd + +values = pd.Series(data=[19, 33, 12, 5.7, 61, 14]) +double = values * 2 # 38, 66, 24, 11.4, 122, 28 +even = values % 2 == 0 # F, F, T, F, F, T +``` + +---- + +#### Séries de type `object` (chaînes) + +Sur une série de type chaîne de caractères, on aura davantage envie d'appliquer +simplement des méthodes de base disponibles sur le type `str`{.python}. + +Le moyen d'utiliser lesdites méthodes consiste à passer par un attribut de la +série nommé `.str`{.python} : + +```python {.numberLines} +import pandas as pd + +names = pd.Series(data=["Élodie", "Hubert", "Céleste", "Jacques", "Albéric", "42", "1137"]) +lower = names.str.lower() # récupérer une série avec les noms en minuscule +digits = names.str.isdigit() +containing_plop = names.str.contains("plop") # Renvoie une série de booléens +``` + +---- + +#### Séries de type `datetime64[ns]` (dates) + +Sur une série de type dates, il y a aussi des outils qui permettent d'extraire des +informations intéressantes depuis des dates; vous pouvez extraire le jour du mois, +le numéro de jour dans l'année, etc. + +Le moyen d'utiliser lesdites méthodes consiste à passer par un attribut de la +série nommé `.dt`{.python} (pour **datetime**) : + +```python {.numberLines} +import pandas as pd + +dates = pd.Series(data=["2001-01-19", "2012-12-21", "2022-04-30", "2016-10-16", "2024-06-01"], dtype="datetime64[ns]") +days = dates.dt.day # récupérer une série avec le numéro de jour +ordinals = dates.dt.day_of_year # récupérer une série avec le numéro de jour de l'année +day_names = dates.dt.day_name(locale="") # récupérer une série avec le nom de jour dans la langue du système +``` diff --git a/documentation/new-01.5-pandas-dataframe.md b/documentation/new-01.5-pandas-dataframe.md new file mode 100644 index 0000000..5b1af5b --- /dev/null +++ b/documentation/new-01.5-pandas-dataframe.md @@ -0,0 +1,1408 @@ +--- +title: Pandas +author: Steve Kossouho +--- + +# Manipuler et comprendre les dataframes + +---- + +## Propriétés d'un dataframe + +Un objet `DataFrame`{.python} dans Pandas est un objet représentant un tableau de valeurs. On peut l'imaginer comme une table de base de données, où chaque colonne du document est une `Series`{.python}, avec son propre type. Les objets `DataFrame`{.python} +possèdent deux index; un pour étiqueter les éléments des colonnes de contenu, et un autre pour étiqueter... les colonnes elles-mêmes. + + +![Exemple de dataframe](assets/images/structures-dataframe-demonstration.drawio.png) + +---- + +## Notions et nomenclature + +Lorsque vous manipulez un `DataFrame`{.python}, il faut avoir conscience de certaines notions : + +1. Un `DataFrame`{.python} possède deux index, un pour les lignes (vertical), et un pour les colonnes (horizontal). +2. L'index des lignes est un attribut nommé `index`{.python}. +3. L'index des colonnes est un attribut nommé `columns`{.python}. +4. Le contenu d'un `DataFrame`{.python} peut être modifié directement. + +---- + +## Quand obtient-on un dataframe ? + +Un objet de type `DataFrame`{.python} s'obtient naturellement en le créant manuellement. +Les autres cas de figure où l'on récupère un `DataFrame`{.python} sont par exemple, les cas où : + +1. On extrait une ou plusieurs lignes d'un `DataFrame`{.python}; +2. On extrait une ou plusieurs colonnes d'un `DataFrame`{.python}; +3. On utilise une méthode de transformation sans agrégation sur un `DataFrame`{.python}; +4. On lit un `DataFrame`{.python} depuis un document CSV, XLSX, etc… + +---- + +## Types de données dans Pandas (rappel) + +Le stockage de données en mémoire avec Pandas proposera de manipuler des entrées, dans des types spécifiques à Pandas : + +- `object` : données de types divers, dont chaînes de caractères +- `int64/32/16` : nombres entiers (64 bits maximum) +- `Int64/32/16` : nombres entiers (avec prise en charge des nan) +- `float64` : nombres à virgule flottante (64 bits) +- `complex128` : même les nombres complexes (64 et 64 bits) +- `bool` : valeurs booléennes +- `boolean` : valeurs booléennes (avec prise en charge des nan) +- `datetime64[ns]` : représentation d'une date +- `datetime64[ns, UTC]` : représentation d'une date, avec décalage horaire (UTC) +- `timedelta64` : représentation d'un intervalle de temps +- `category`: choix de valeurs textuelles + +---- + +La bibliothèque `pyarrow` propose d'autres types, utilisables par Pandas, pour stocker des données. +Entre autres, vous pouvez trouver les types suivant : + +- `int8/16/32/64[pyarrow]` : nombres entiers (avec prise en charge des nan) +- `uint8/16/32/64[pyarrow]` : nombres entiers sans signe (avec prise en charge des nan) +- `float16/32/64[pyarrow]` : nombres à virgule flottante (avec prise en charge des nan) +- `timestamp[ns]` : date et heure + +---- + +## Type de données d'un `DataFrame`{.python} + +Un objet `DataFrame`{.python} possède plusieurs colonnes que l'on peut considérer comme des `Series`{.python}. +Chaque colonne possède donc son propre type, et il est toujours possible de connaître le type des colonnes d'un dataframe +en accédant à son attribut `dtypes` (data type); l'attribut renvoie une série de types Pandas, étiquetés avec l'index +d'une colonne : + +```python {.numberLines} +import pandas as pd + +data = pd.DataFrame(data={"product": ["Pen", "Ink", "Paper", "Ruler"], "price": [.99, 20.49, 5.99, 1.99]}) +print(data.dtypes) # affiche une série ["object", "float64"] avec l'index ["product", "price"] +``` + +---- + +## Créer un `DataFrame`{.python} + +Nous avons vu que nous pouvons créer simplement des séries, l'équivalence est vraie pour les dataframes. Nous +avons à disposition plusieurs moyens de renseigner leurs données : + +- Des tableaux Numpy à deux dimensions +- Des séquences de séquences Python (`int`{.python}, `float`{.python}, `str`{.python}, `datetime.datetime`{.python}…) +- Des dictionnaires Python (dont les valeurs peuvent être des `Series`) +- Des listes de dictionnaires Python +- Des `Series`{.python} peuvent servir de source et être concaténées horizontalement + +---- + +### Créer un `DataFrame` avec des listes + +Créer un dataframe nécessite au minimum de passer deux dimensions de données. Ainsi, passer une liste +de listes est une option raisonnable; chaque liste contenue dans la liste principale représentera une **ligne** +du document (une valeur par colonne). + +```python {.numberLines} +import pandas as pd + +# Créer un DataFrame via une liste de listes +data = pd.DataFrame(data=[ + ["Disquette 3,5\" 1,39Mo Imation", 2.95, 1996], + ["Magnétoscope LP Hitachi", 99.95, 1994], + ["Lecteur Disque Zip Iomega", 169.95, 1994], +]) +``` + +Les lignes et les colonnes du document posséderont un index par défaut (des index numériques débutant à `0`). + +---- + +### Créer un `DataFrame` avec un dictionnaire (recommandé) + +Les dictionnaires ont pour avantage d'être définis avec des clés, qui seront utilisées par Pandas comme valeurs d'index +de colonnes. + +```python {.numberLines} +import pandas as pd + +# Créer un DataFrame avec un dictionnaire +data = pd.DataFrame(data={ + "product": ["Disquette 3,5\" 1,39Mo Imation", "Magnétoscope LP Hitachi", "Lecteur Disque Zip Iomega"], + "price": [2.95, 99.95, 169.95], + "year": [1996, 1994, 1994] +}) +``` + +---- + +### Créer un `DataFrame` avec une liste de dictionnaires + +Passer une liste de dictionnaires, un dictionnaire par ligne, a pour intérêt principal de permettre de renseigner +uniquement les colonnes qui nous intéressent. + +```python {.numberLines} +import pandas as pd + +# Créer un DataFrame avec une liste de dictionnaires +data = pd.DataFrame(data=[ + {"product": "Disquette 3,5\" 1,39Mo Imation", "price": 2.95}, + {"product": "Magnétoscope LP Hitachi", "year": 1994}, + {"product": "Lecteur Disque Zip Iomega", "price": 169.95, "year": 1994} +]) +``` + +---- + +### Créer un `DataFrame` avec des `Series`{.python} + +Si vous souhaitez créer un `DataFrame`{.python} avec des objets de type `Series`{.python}, la méthode la +plus accessible consiste à les concaténer... dans le sens des colonnes. + +```python {.numberLines} +import pandas as pd + +products = pd.Series(data=["Disquette 3,5\" 1,39Mo Imation", "Magnétoscope LP Hitachi", "Lecteur Disque Zip Iomega"], name="product") +prices = pd.Series(data=[2.95, 99.95, 169.95], name="price") +years = pd.Series(data=[1996, 1994, 1994], name="year") + +data = pd.concat([products, prices, years], axis="columns") +``` + +Les noms donnés aux séries (via l'argument `name=`{.python})seront utilisés comme identifiants dans l'index des colonnes du `DataFrame`{.python} résultant. + +---- + +### Spécifier les index d'un `DataFrame`{.python} + +Si le jeu de données que vous passez à la création manuelle d'un `DataFrame`{.python} ne suffit pas à préciser tout ce +dont vous avez besoin, notamment au niveau des index, vous pouvez préciser spécifiquement les valeurs desdits index. + +```python {.numberLines} +import pandas as pd + +# Créer un DataFrame via une liste de listes +data = pd.DataFrame( + data=[ + ["Disquette 3,5\" 1,39Mo Imation", 2.95, 1996], + ["Magnétoscope LP Hitachi", 99.95, 1994], + ["Lecteur Disque Zip Iomega", 169.95, 1994], + ], + index=["floppy", "vhs", "zip"], # index des lignes + columns=["product", "price", "year"] # index des colonnes +) +``` + +---- + +## Extraire des informations d'un `DataFrame`{.python} + +Si nous avons vu précédemment comment extraire des informations d'une série, et que certains éléments de syntaxe +doivent pouvoir s'appliquer de façon similaire aux `DataFrame`{.python}, nous devons aborder les cas utiles en détail. + +---- + +### Taille d'un `DataFrame`{.python} + +Un objet `DataFrame`{.python} est un objet en deux dimensions. Il possède donc une largeur (nombre de colonnes) et une hauteur (nombre de lignes). + +```python {.numberLines} +import pandas as pd + +# Créer un DataFrame via une liste de listes +data = pd.DataFrame(...) +# Affiche un tuple (lignes, colonnes) +print(data.shape) +# Affiche le nombre de cellules du document +print(data.size) +``` + +L'attribut `.shape`{.python} permet de récupérer un tuple représentant dans l'ordre la hauteur puis le nombre de colonnes du contenu. +L'attribut `.size`{.python}, lui, renvoie le nombre de cellules au total dans le document (_largeur * hauteur_) + +---- + +### Index des lignes et des colonnes + +Récupérer les index des lignes et des colonnes, c'est très simple; il suffit de récupérer des +attributs du `DataFrame`{.python} nommés `index`{.python} et `columns`{.python} : + +```python {.numberLines} +import pandas as pd + +# Créer un DataFrame avec un dictionnaire +data = pd.DataFrame(data={ + "product": ["Disquette", "Magnétoscope", "Lecteur Zip"], + "price": [2.95, 99.95, 169.95], + "year": [1996, 1994, 1994] +}) +# Afficher l'index de colonnes, puis des lignes +# Un index est une séquence et vous pouvez récupérer des valeurs à des positions numériques +print(data.columns) +print(data.index) +``` + +---- + +### Cellule à une position précise + +Récupérer une cellule unique est possible grâce à l'attribut `at[]`{.python} des objets de type `DataFrame`{.python}. +Ce n'est pas une méthode : il s'utilise comme les attributs `.loc[]`{.python} et `.iloc[]`{.python}, en passant +comme clé un tuple contenant _l'index de ligne_, puis _celui de colonne_ : + +```python {.numberLines} +import pandas as pd + +# colonnes : "product", "price" et "year" +# index de lignes : 0, 1 et 2 +data = pd.DataFrame(data={ + "product": ["Disquette", "Magnétoscope", "Lecteur Zip", "CD"], + "price": [2.95, 99.95, 169.95, 4.29], + "year": [1996, 1994, 1994, 1997] +}) +# Récupérer la cellule à la ligne 0, colonne "product" +print(data.at[(0, "product")]) +``` + +---- + +### Une ligne de données + +Quand on pense à une feuille de calcul dans un tableur Excel, on a tout intérêt à pouvoir, pour nos calculs, +extraire des lignes ou des colonnes. Commençons par les lignes : + +---- + +#### Ligne de données identifiée par un index + +Extraire une ligne d'un `DataFrame`{.python} peut se faire depuis les valeurs d'index associées aux lignes du document. Vous récupérez un objet `Series`{.python} dont les valeurs d'index correspondent aux colonnes : + +```python {.numberLines} +import pandas as pd + +# Créer un DataFrame via une liste de listes +data = pd.DataFrame( + data=[ + ["Disquette 3,5\" 1,39Mo Imation", 2.95, 1996], + ["Magnétoscope LP Hitachi", 99.95, 1994], + ["Lecteur Disque Zip Iomega", 169.95, 1994], + ], + index=["floppy", "vhs", "zip"], # index des lignes + columns=["product", "price", "year"] # index des colonnes +) +# Extraire une série, via l'attribut loc +row = data.loc["vhs"] +print(row, type(row)) +``` + +**Note** : _Les valeurs par défaut d'index pour un `DataFrame`{.python} ou une `Series`{.python} +sont toujours des nombres séquentiels démarrant à `0`{.python}._ + +---- + +#### Ligne de données identifiée par une position numérique + +Indépendamment des index des lignes, vous pouvez récupérer des lignes d'un `DataFrame`{.python} par leur +position (indexée à `0`{.python}) avec l'attribut `.iloc`{.python}. Vous récupérez également un objet `Series`{.python} +dont les valeurs d'index correspondent aux colonnes : + +```python {.numberLines} +import pandas as pd + +# Créer un DataFrame via une liste de listes +data = pd.DataFrame( + data=[ + ["Disquette 3,5\" 1,39Mo Imation", 2.95, 1996], + ["Magnétoscope LP Hitachi", 99.95, 1994], + ["Lecteur Disque Zip Iomega", 169.95, 1994], + ], + index=["floppy", "vhs", "zip"], # index des lignes + columns=["product", "price", "year"] # index des colonnes +) +# Extraire la troisième ligne, via l'attribut iloc +row = data.iloc[2] +print(row, type(row)) +``` + +---- + +### Colonne de données + +L'API proposée par Pandas sur les `DataFrame`{.python} est très simple lorsqu'il faut extraire une **colonne**; +il suffit globalement de considérer le `DataFrame`{.python} comme un simple dictionnaire. +La clé à utiliser est la valeur d'index de la colonne à extraire : + +```python {.numberLines} +import pandas as pd + +data = pd.DataFrame( + data={ + "person": ["Marie Martin", "Pierre Plâtrier", "Jules Joffrin"], + "city": ["Marseille", "Paris", "Juvisy"], + "earn": [3000.0, 2800.0, 2600.0] + } +) + +# Extraire la colonne "person" vers une série +people = data["person"] +print(people) +``` + +---- + +### Plusieurs lignes de données + +Nous pouvons extraire d'un `DataFrame`{.python} plusieurs lignes de données de notre choix en se servant des +fonctionnalités des attributs `.loc`{.python} et `.iloc`{.python}. +La différence avec les cas précédents est le type de la clé utilisée pour extraire du contenu : + +```python {.numberLines} +import pandas as pd + +data = pd.DataFrame( + data={ + "person": ["Marie Martin", "Pierre Plâtrier", "Jules Joffrin"], + "city": ["Marseille", "Paris", "Juvisy"], + "earn": [3000.0, 2800.0, 2600.0] + }, + index=["M", "P", "J"] +) + +# Extraire les première et troisième lignes +rows1 = data.loc[["M", "J"]] # la clé est une LISTE de valeurs d'index +rows2 = data.iloc[[0, 2]] # la clé est une LISTE de numéros de ligne +print(rows1, rows2, sep="\n") +``` + +Pour extraire plusieurs lignes, vous devez utiliser un objet de type `list`{.python} et uniquement `list`{.python}. +Le résultat obtenu est un objet de type `DataFrame`{.python}. + +---- + +### Plusieurs colonnes de données + +Avec exactement le même état d'esprit que pour extraire plusieurs lignes, si vous utilisez le `DataFrame`{.python} +comme un dictionnaire et utilisez une clé qui est un objet de type `list`{.python}, vous pouvez extraire plusieurs colonnes à la fois. + +```python {.numberLines} +import pandas as pd + +data = pd.DataFrame( + data={ + "person": ["Marie Martin", "Pierre Plâtrier", "Jules Joffrin"], + "city": ["Marseille", "Paris", "Juvisy"], + "earn": [3000.0, 2800.0, 2600.0] + }, + index=["M", "P", "J"] +) + +# Extraire les première et troisième colonnes +# La clé à utiliser est une LISTE de valeurs d'index de colonne +cols1 = data[["person", "earn"]] +# Je peux extraire les noms de colonne numériquement via .columns +cols2 = data[data.columns[[0, 2]]] +print(cols1, cols2, sep="\n") +``` + +---- + +### Lignes et colonnes + +Pandas est une bibliothèque ingénieuse; elle vous autorise, avec peu de changements, à extraire d'un +`DataFrame`{.python} des lignes, mais aussi des colonnes en même temps. + +Pour cela vous pouvez utiliser les attributs `.loc[]`{.python} et `.iloc[]`{.python}, et utiliser comme clé +un objet de type `tuple`{.python} à **deux** éléments (et pas une liste) : + +```python {.numberLines} +import pandas as pd + +data = pd.DataFrame( + data={ + "person": ["Marie Martin", "Pierre Plâtrier", "Jules Joffrin"], + "city": ["Marseille", "Paris", "Juvisy"], + "earn": [3000.0, 2800.0, 2600.0] + }, + index=["M", "P", "J"] +) + +# Sélectionner par valeurs d'index +selection1 = data.loc[("M", "person")] +# Sélectionner par positions numériques +selection2 = data.iloc[(0, 0)] +print(selection1, selection2, sep="\n") +``` + +---- + +### Bon à savoir : vues et copies + +Lorsque vous extrayez une partie d'un `DataFrame`{.python}, vous obtenez généralement un objet de type `DataFrame`{.python}. +Cependant, il faut savoir que par défaut, le nouveau `DataFrame`{.python} est une **vue** sur le `DataFrame`{.python} initial. +Cela veut dire que si vous modifiez le contenu du `DataFrame`{.python} initial, le contenu du `DataFrame`{.python} extrait sera aussi modifié, et vice versa. + +Pour obtenir une copie des données du `DataFrame`{.python} extrait, il faut utiliser la méthode `.copy()`{.python}. + +```python {.numberLines} +import pandas as pd + +data = pd.DataFrame( + data={ + "fruit": ["Banane", "Orange", "Mangue"], + "price": [0.95, 0.85, 1.05], + "ean": ["1234567890123", "4567890123456", "7890123456789"] + }, + index=["B", "O", "M"]) +# Extraire une partie du DataFrame et obtenir une vue +selection1 = data.loc[("M", "fruit")] +# Extraire une partie du DataFrame et obtenir une copie +selection2 = data.loc[("M", "fruit")].copy() +``` + +---- + +### Lignes avec des conditions + +Dans cette section, l'objectif est de pouvoir dire à Pandas : « Filtre-moi les lignes de ce `DataFrame`{.python} en gardant +celles où une condition est remplie ». Par exemple : + +```python {.numberLines} +import pandas as pd + +data = pd.DataFrame(data={ + "product": ["Eau", "Air", "Feu", "Terre"], + "price": [10, 20, 30, 40] +}) +``` + +Imaginons que je souhaite filtrer ce `DataFrame`{.python} pour ne garder que les lignes où la colonne `price` +possède une valeur supérieure à `20`. Pour pouvoir bien le faire, il faut comprendre le raisonnement de Pandas. + +---- + +#### Appliquer une condition à une colonne + +Pandas permet d'appliquer une condition à une `Series`{.python} et d'obtenir une suite de `bool`{.python}. +Par exemple, je souhaite savoir quelles valeurs de la série sont supérieures à 20 : + +```python {.numberLines} +import pandas as pd + +data = pd.Series(data=[1, 23, 24, 5, 17, 19, 46]) +filtered = (data > 20) # cette comparaison génère une suite de booléens +``` + +La variable `filtered`{.python} contiendra une série avec les valeurs `[False, True, True, False, False, False, True]`{.python}, +et des valeurs d'index récupérées de la série originale. +Tous les opérateurs de comparaison de Python peuvent être utilisés sur une `Series`{.python} et renvoient une série de booléens. + +---- + +#### Utiliser une série de booléens pour filtrer un `DataFrame`{.python} + +Les séries de booléens, qu'elles soient obtenues via une série ou manuellement, +servent à filtrer les lignes d'un `DataFrame`{.python} : + +```python {.numberLines} +import pandas as pd + +data = pd.DataFrame(data={ + "product": ["Eau", "Air", "Feu", "Terre"], + "price": [10, 20, 30, 40] +}) +# L'expression est une série de booléens de la même taille +# que le DataFrame. Pour chaque valeur True, on garde la ligne +# correspondante. +cheapest = data.loc[data["price"] < 25] +``` + +Cette expression se lit aussi naturellement « Récupérer les lignes de `data` où la +colonne `price` de `data` est inférieure à 25 ». + +---- + +#### Séries de booléens : opérateurs + +Les opérateurs de comparaison fonctionnent pour récupérer des séries de booléens : + +```python {.numberLines} +import pandas as pd + +data = pd.Series(data=[1, 2, 3, 4, 5, 6]) +is_even = (data % 2) == 0 # F, T, F, T, F, T +is_high = data > 3 # F, F, F, T, T, T +# Filtrer les données avec les séries de booléens +high_data = data.loc[is_high] # 4, 5, 6 +even_data = data.loc[is_even] # 2, 4, 6 +``` + +---- + +#### Séries de booléens : opérateurs booléens + +Vous pouvez organiser logiquement vos filtres avec les opérateurs `OU` et `ET`; par exemple, garder les lignes +où le nombre est pair, **et** où le même nombre est supérieur à 3 : + +```python {.numberLines} +import pandas as pd + +data = pd.Series(data=[1, 2, 3, 4, 5, 6]) +is_even = (data % 2) == 0 # F, T, F, T, F, T +is_high = data > 3 # F, F, F, T, T, T +high_data = data.loc[is_high & is_even] # 4, 6 +``` + +On peut utiliser les opérateurs binaires `&`{.python} (`and`) et `|`{.python} (`or`) pour combiner les séries de booléens. +Le résultat est une série de booléens de la même longueur. + +---- + +#### Séries de booléens : chaînes + +Si on voulait filtrer des lignes de `Series`{.python} ou de `DataFrame`{.python} sur un critère de chaîne, +on peut imaginer utiliser l'opérateur `==`{.python} : + +```python {.numberLines} +import pandas as pd + +data = pd.Series(data=["matt", "jenn", "igor", "alex", "mila"]) +# Ici on garde True si la valeur est exactement "mila" +mila_data = data.loc[data == "mila"] +``` + +---- + +C'est bien, mais cela ne suffit pas si on recherche, par exemple, les lignes dans lesquelles le texte **contient** une chaîne, voire répond +à une _expression régulière_. Il existe pour la série un attribut `str` +qui permet d'appliquer diverses méthodes de chaînes de caractères, dont une méthode `contains()`{.python} : + +```python {.numberLines} +import pandas as pd + +data = pd.Series(data=["matt", "jenn", "igor", "alex", "mila"]) +# Ici on garde True si la valeur contient un "i" minuscule +filter_data1 = data.loc[data.str.contains("i")] +# Ici on garde True si la valeur contient un "i" (ignore la casse) +filter_data2_a = data.loc[data.str.contains("(?i)i")] +filter_data2_b = data.loc[data.str.contains("i", case=False)] +# Si vous connaissez les regex, le reste marche aussi : +# Ici reconnaître les chaînes où un caractère apparaît 2 fois de suite +filter_data3 = data.loc[data.str.contains(r"(\w)\1")] +``` + +[Apprendre les Regex avec RegexLearn](https://regexlearn.com/fr) + +---- + +#### Séries de booléens : infos de dates + +Certaines séries peuvent contenir des valeurs de type `datetime64[ns]`{.python}, +et on peut filtrer là-dessus; par exemple, on peut imaginer filtrer un dataframe +sur les lignes où une colonne concerne un mois de juin : + +```python {.numberLines} +import pandas as pd + +data = pd.DataFrame(data={ + "order": ["A15", "B84", "D25", "B43"], + "date": pd.to_datetime(["2024-06-10", "2021-02-14", "2023-06-25", "2024-11-30"]) +}) +june_dates = (data["date"].dt.month == 6) +june_data = data.loc[june_dates] +``` + +Dans cet exemple, on utilise l'attribut `dt`{.python} d'une série pour avoir accès à des attributs et des méthodes d'extraction d'informations sur une valeur date. +L'attribut est documenté ici : + +[Attributs de dt](https://pandas.pydata.org/docs/reference/api/pandas.Series.dt.html) + +---- + +| Méthode/Attribut | Explication succincte | +|---------------------|----------------------------------------------------| +| [dt.date][1] | Renvoie la date (sans l'heure) pour chaque élément | +| [dt.time][2] | Renvoie l'heure (sans la date) pour chaque élément | +| [dt.year][3] | Renvoie l'année de chaque élément | +| [dt.month][4] | Renvoie le mois de chaque élément (1-12) | +| [dt.day][5] | Renvoie le jour du mois de chaque élément (1-31) | +| [dt.hour][6] | Renvoie l'heure de chaque élément | +| [dt.minute][7] | Renvoie les minutes de chaque élément | +| [dt.second][8] | Renvoie les secondes de chaque élément | +| [dt.microsecond][9] | Renvoie les microsecondes de chaque élément | +| [dt.nanosecond][10] | Renvoie les nanosecondes de chaque élément | + +---- + +| Méthode/Attribut | Explication succincte | +|---------------------------|------------------------------------------------------------------------| +| [dt.weekday][12] | Renvoie le jour de la semaine pour chaque élément (0=Monday, 6=Sunday) | +| [dt.dayofweek][13] | Alias de `weekday`, renvoie le jour de la semaine | +| [dt.dayofyear][14] | Renvoie le jour de l'année pour chaque élément (1-365/366) | +| [dt.quarter][15] | Renvoie le trimestre de l'année pour chaque élément | +| [dt.is_month_start][16] | Renvoie un booléen indiquant si la date est le début du mois | +| [dt.is_month_end][17] | Renvoie un booléen indiquant si la date est la fin du mois | +| [dt.is_quarter_start][18] | Renvoie un booléen indiquant si la date est le début du trimestre | +| [dt.is_quarter_end][19] | Renvoie un booléen indiquant si la date est la fin du trimestre | +| [dt.is_year_start][20] | Renvoie un booléen indiquant si la date est le début de l'année | + +---- + +| Méthode/Attribut | Explication succincte | +|------------------------|----------------------------------------------------------------| +| [dt.is_year_end][21] | Renvoie un booléen indiquant si la date est la fin de l'année | +| [dt.is_leap_year][22] | Renvoie un booléen indiquant si l'année est bissextile | +| [dt.days_in_month][23] | Renvoie le nombre de jours dans le mois pour chaque élément | +| [dt.tz][24] | Renvoie le fuseau horaire de chaque élément | +| [dt.freq][25] | Renvoie la fréquence des éléments si elle est définie | +| [dt.strftime][26] | Formate les dates selon une chaîne de format spécifiée | +| [dt.to_period][27] | Convertit en période périodique (e.g., année, trimestre) | +| [dt.to_pydatetime][28] | Convertit en objets datetime de Python natif | +| [dt.total_seconds][29] | Renvoie la durée totale en secondes pour les objets timedelta | +| [dt.normalize][30] | Normalise les dates à minuit (enlevant les composants d'heure) | + +---- + +| Méthode/Attribut | Explication succincte | +|----------------------|----------------------------------------------------------------------------| +| [dt.tz_localize][31] | Localise les dates selon un fuseau horaire spécifié | +| [dt.tz_convert][32] | Convertit les dates à un autre fuseau horaire | +| [dt.round][33] | Arrondit les dates à une fréquence spécifiée | +| [dt.floor][34] | Abaisse les dates à la fréquence spécifiée | +| [dt.ceil][35] | Elève les dates à la fréquence spécifiée | +| [dt.month_name][36] | Renvoie le nom du mois pour chaque élément | +| [dt.day_name][37] | Renvoie le nom du jour de la semaine pour chaque élément | +| [dt.isocalendar][38] | Renvoie les composants ISO de la date (année, semaine, jour de la semaine) | + +[1]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.date.html +[2]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.time.html +[3]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.year.html +[4]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.month.html +[5]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.day.html +[6]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.hour.html +[7]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.minute.html +[8]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.second.html +[9]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.microsecond.html +[10]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.nanosecond.html +[11]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.week.html +[12]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.weekday.html +[13]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.dayofweek.html +[14]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.dayofyear.html +[15]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.quarter.html +[16]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.is_month_start.html +[17]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.is_month_end.html +[18]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.is_quarter_start.html +[19]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.is_quarter_end.html +[20]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.is_year_start.html +[21]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.is_year_end.html +[22]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.is_leap_year.html +[23]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.days_in_month.html +[24]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.tz.html +[25]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.freq.html +[26]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.strftime.html +[27]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.to_period.html +[28]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.to_pydatetime.html +[29]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.total_seconds.html +[30]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.normalize.html +[31]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.tz_localize.html +[32]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.tz_convert.html +[33]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.round.html +[34]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.floor.html +[35]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.ceil.html +[36]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.month_name.html +[37]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.day_name.html +[38]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.dt.isocalendar.html + + +---- + +Les méthodes de cet attribut sont documentées individuellement (voir les liens dans l'index de la page). +Vous pouvez obtenir l'autocomplétion de cet attribut **uniquement** en installant le paquet suivant : + +```bash {.numberLines} +pip install pandas-stubs +``` + +---- + +Exemple d'utilisation de `dt`{.python} + +```python {.numberLines} +import pandas as pd + +# Les dates contiennent des minutes et j'aimerais arrondir +# à l'heure la plus proche pour dédupliquer des informations +data = pd.DataFrame(data={ + "order": ["A15", "B84", "D25", "B43"], + "date": pd.to_datetime(["2024-06-10T04:31", "2021-02-14T13:55", "2023-06-25T06:14", "2024-11-30T00:13"]) +}) +data["hour"] = data.dt.round("H") +print(data) +``` + +---- + +### Dédupliquer des lignes + +Autre type d'extraction : imaginons que vous lisiez des données depuis un fichier Excel dans un `DataFrame`{.python}, et vous souhaitez en dédupliquer des lignes. Vous considérez par exemple que deux lignes sont un doublon si elles ont la même valeur sur la troisième colonne. C'est possible de le faire grâce à une méthode `drop_duplicates`{.python}. + +---- + +Considérons le tableau suivant : + +| Prénom | Nom | Âge | +|:---------|:-------|----:| +| Alice | Leroy | 25 | +| Bob | Dupont | 30 | +| Alice | Leroy | 27 | +| David | Martin | 40 | +| Eva | Joly | 35 | +| Bob | Dupont | 26 | + +---- + +Dans ce tableau, Bob Dupont et Alice Leroy apparaissent en doublon, +même si la colonne `Âge` n'a pas la même valeur à chaque ligne en doublon. + +Nous pouvons dédupliquer les lignes du document avec l'extrait suivant : + +```python {.numberLines} +import pandas as pd + +data = pd.read_csv("names.csv") +# Dédupliquer en considérant uniquement les colonnes Nom et Prénom +deduplicated = data.drop_duplicates(subset=["Prénom", "Nom"]) +``` + +---- + +Dans ce cas de figure, les lignes considérées comme dupliquées seront… dédupliquées, +en conservant par défaut la première ligne dupliquée trouvée. Il est possible via l'argument `subset=`{.python} de choisir +quelles colonnes sont considérées pour être comparées dans le `DataFrame`{.python}. Par exemple : + +```python {.numberLines} +import pandas as pd + +data = pd.read_csv("names.csv") +# Dédupliquer en considérant uniquement les colonnes Nom et Prénom +deduplicated = data.drop_duplicates(subset=["Prénom", "Nom"]) +``` + +Un argument `keep=`{.python} permet d'indiquer quelles lignes conserver parmi les doublons, et possède par +défaut la valeur `"first"`{.python}, qui ne conserve que la première ligne en doublon (en partant du sommet du document). + +---- + +#### Suppression des doublons + +L'argument `keep=`{.python} de la méthode `drop_duplicates()`{.python} +permet d'indiquer à Pandas comment dédupliquer les entrées. Il accepte trois valeurs : `"first"`{.python}, `"last"`{.python} et `False`{.python}. + +- `"first"`{.python} : seule la première entrée de chaque groupe de doublons est conservée (en partant du haut du document) +- `"last"`{.python} : seule la dernière entrée du groupe de doublons est conservée (en partant du bas du document) +- `False`{.python} : si une ligne apparaît en doublon, elle et tous ses clones sont retirés du résultat + +---- + +#### Conservation des doublons + +Vous pourriez être tenté depuis un jeu de données de récupérer uniquement les doublons afin de les analyser. +Cette opération peut se faire en deux temps, grâce à la méthode `.duplicated()`{.python} d'un `DataFrame`{.python}. + +La méthode vous renvoie une série de booléens, indiquant si chaque ligne est considérée comme un doublon. Plusieurs +stratégies existent pour considérer un doublon : + +- Toutes les valeurs sauf la première occurrence (`"first"`) +- Toutes les valeurs sauf la dernière occurrence (`"last"`) +- Toutes les valeurs (`False`{.python}) + +```python {.numberLines} +import pandas as pd + +data = pd.read_csv("names.csv") +# Dédupliquer en considérant uniquement les colonnes Nom et Prénom +duplicated = data.duplicated(subset=["Prénom", "Nom"], keep=False) +duplicates = data.loc[duplicated] +``` + +---- + +### Trier les lignes d'un `DataFrame`{.python} + +Vous pouvez trier les lignes d'un `DataFrame`{.python} ou d'une `Series`{.python} en personnalisant +sur quelles colonnes vous souhaitez effectuer le tri, et dans quel sens vous souhaitez effectuer ledit tri (croissant ou décroissant) : + +```python {.numberLines} +import pandas as pd + +df = pd.DataFrame(data={ + "prenom": ["Gisèle", "Jocelyne", "Charles", "Valentin"], + "nom": ["Garnier", "Jones", "Chancel", "Verdier"], + "numero": [8, 31, 23, 14] +}) + +# Récupérer un dataframe trié sur nom puis prénom, dans l'ordre croissant +result1 = df.sort_values(by=["nom", "prenom"]) +print(result1) +# Récupérer un dataframe trié sur nom puis prénom, dans l'ordre croissant, puis décroissant +result2 = df.sort_values(by=["nom", "prenom"], ascending=[True, False]) +print(result2) +``` + +---- + +### Récapitulatif + +Filtrer un `DataFrame`{.python} assigné à une variable `df`{.python} : + +Attributs et usages principaux : + +- `df.loc[]`{.python} : extraire des lignes +- `df.iloc[]`{.python} : extraire des lignes +- `df.at[rowindex, colindex]`{.python} : extraire une cellule +- `df[]`{.python} : extraire des colonnes + +Dans tous les cas sauf pour `df.at`{.python}, la clé peut être un des objets suivants : + +- Index de ligne ou de colonne (`a`{.python}); +- slice Python (`départ:fin:pas`{.python}); +- liste de clés de lignes/colonne ou numéro de ligne (`[a, b, c]`{.python}); +- tuple à deux éléments (`(ligne(s), colonne(s))`{.python}). + +---- + +## Valeurs vides + +Au même titre qu'un document Excel, un `DataFrame`{.python} peut contenir des cellules vides. +Malheureusement, nous sommes en Python et la notion de vide doit être représentée par une valeur. +Et il ne s'agit pas de `None`{.python}, mais d'une valeur `numpy.nan`{.python} (not a number). + +Notez que le type de cette valeur spéciale « vide » est `float`{.python} et joue sur le type de sa colonne +(ex. une valeur `NaN`{.python} dans une colonne de type `int64`{.python} transforme la colonne en lui donnant le type `float64`{.python}). + +[Not a Number sur Wikipedia](https://en.wikipedia.org/wiki/NaN) + +---- + +### Filtrage des valeurs vides + +Pour un objet `Series`{.python} ou `DataFrame`{.python}, deux méthodes vous permettent d'obtenir un objet de même type et de mêmes dimensions, rempli d'une carte de booléens indiquant les valeurs vides (`NaN`{.python}). + +```python {.numberLines} +import pandas as pd +from numpy import nan + +df = pd.DataFrame(data={"nom": ["Jose", nan, "Pepe"], "age": [nan, 10, 24]}) +# Dataframe de booléens à deux colonnes où chaque valeur vide de df est True +nan_map = df.isnull() +# Dataframe de booléens à deux colonnes où chaque valeur de df remplie est True +set_map = df.notnull() +``` + +---- + +À quoi peuvent bien servir les cartes de booléens obtenues sous forme de `DataFrame`{.python} ? + +1. Utilisées comme clés directement sur un `DataFrame`{.python} de mêmes dimensions, nous récupérons un `DataFrame`{.python} où les valeurs à la même position qu'une valeur `False`{.python} dans la carte sont remplacées par `NaN`{.python}; +2. si une colonne est utilisée comme clé pour l'attribut `.loc`{.python} d'un `DataFrame`{.python}, le filtrage classique est utilisé : une ligne à la même position qu'un `False`{.python} n'est pas conservée dans le résultat; +3. utiliser des méthodes d'agrégation numériques comme `.sum()`{.python} ou `.mean()`{.python} permet respectivement de connaître le nombre de valeurs `True`{.python} par colonne de booléens ou le ratio de valeurs `True`{.python} par colonne de booléens (car `False`{.python} vaut `0` et `True`{.python} vaut `1`). + +---- + +### Méthodes `.any()`{.python}, `.all()`{.python} et `.sum()`{.python} + +Les cartes booléennes que vous pouvez obtenir avec les méthodes `.isna()`{.python} et `.notna()`{.python} possèdent des méthodes d'agrégation utiles : + +```python {.numberLines} +import pandas as pd +from numpy import nan + +df = pd.DataFrame(data={ + "nom": ["Jose", nan, "Pepe"], + "age": [nan, 10, 24] +}) +# Dataframe de booléens à deux colonnes où chaque valeur initialement vide +# est représentée par un booléen True +nan_map: pd.DataFrame = df.isnull() +# L'axe à préciser est celui à réduire +print(nan_map.any(axis="columns")) # Réduire les colonnes d'une ligne +print(nan_map.all()) # Réduire par défaut les lignes d'une colonne +print(nan_map.sum()) # Réduire par défaut les lignes d'une colonne +``` + +---- + +### Supprimer des lignes avec des valeurs inexploitables + +Cela peut arriver sur des données récupérées de diverses sources, d'avoir des informations incomplètes. +Par exemple, une information d'entreprise n'indiquant pas d'adresse de siège (ce qui serait intéressant pour du publipostage). + +---- + +Vous pouvez, sur un `DataFrame`{.python}, retirer des lignes (ou des colonnes) dans lesquelles certaines colonnes ne sont pas renseignées, avec plusieurs stratégies : + +```python {.numberLines} +import pandas as pd +from numpy import nan + +df = pd.DataFrame(data={ + "siret": ["5001234567890", "6001234567890", "7001234567890", "800123456789"], + "nom": ["SARL Marks", "SAS Pareil", "SASU Key", "Péri SCOP"], + "siege": ["Rue des roses", "Rue des baies", nan, nan] +}) +# Créer un nouveau DataFrame dans lequel +# Ne garder que les lignes avec un siège ET un SIRET +df2 = df.dropna(axis="index", how="any", subset=["siege", "siret"]) +# Ne garder que les lignes avec un siège OU un SIRET +df3 = df.dropna(axis="index", how="all", subset=["siege", "siret"]) +# Ne garder que les colonnes sans aucune valeur vide +df4 = df.dropna(axis="columns", how="any") +``` + +---- + +### Conserver les lignes avec des valeurs vides + +Si vous souhaitez traiter ou analyser les lignes qui contiennent des valeurs que vous +considérez non exploitables, vous pouvez les récupérer, en deux temps, en utilisant les +méthodes `.isnull()`{.python} et `.any()`{.python} ou `.all()`{.python} : + +```python {.numberLines} +import pandas as pd +from numpy import nan + +df = pd.DataFrame(data={ + "siret": ["5001234567890", "6001234567890", "7001234567890", "800123456789"], + "nom": ["SARL Marks", "SAS Pareil", "SASU Key", "Péri SCOP"], + "siege": ["Rue des roses", "Rue des baies", nan, nan] +}) + +# Récupérer les lignes avec du vide +empty_map = df.isna() # Récupérer la carte qui dit si chaque valeur du dataframe est un NaN +# Dire pour chaque ligne si une des valeurs est True +# Cela donne une série de booléens qui dit pour chaque ligne si elle contenait une valeur vide +empty_rows = empty_map.any(axis="columns") +# Afficher le résultat du filtre +print(df.loc[empty_rows]) +``` + +---- + +### Insérer des valeurs à la place des valeurs vides + +Si vous souhaitez conserver des données incomplètes, et que votre stratégie pourrait consister à utiliser +des valeurs par défaut lorsqu'une valeur non renseignée est rencontrée, vous avez la possibilité +d'utiliser la méthode `.fillna()`{.python} d'un `DataFrame`{.python}. Elle offre quelques stratégies de remplissage. + +---- + +#### Valeur constante ou dictionnaire + +Lorsque vous utilisez la méthode `.fillna()`{.python} en passant des littéraux, vous récupérez un `DataFrame`{.python} +où toutes les entrées vides de toutes les colonnes sont remplacées. + +```python {.numberLines} +import pandas as pd +from numpy import nan + +df = pd.DataFrame(data={ + "siret": ["5001234567890", nan, "7001234567890", "800123456789"], + "nom": ["SARL Marks", "SAS Pareil", "SASU Key", nan], + "siege": ["Rue des roses", "Rue des baies", nan, nan] +}) +# Créer un nouveau DataFrame dans lequel +# Remplacer les valeurs vides par un texte fixe, partout +df2 = df.fillna("Inconnu") +# Remplacer les valeurs vides par colonne +df3 = df.fillna({"siret": "N/A", "siege": "Inconnu"}) +# Remplir les colonnes avec la dernière valeur non vide trouvée +df4 = df.ffill() # forward fill +# Remplir les colonnes avec la prochaine valeur non vide trouvée +df5 = df.bfill() # backward fill +``` + +---- + +#### `DataFrame`{.python} ou `Series`{.python} + +Utiliser la méthode `.fillna()`{.python} en passant une `Series`{.python} ou un `DataFrame`{.python} est possible, dans certains cas. +Les cas autorisés sont les suivants : + +- Vous appelez la méthode sur un `DataFrame`{.python} et vous passez un `DataFrame`{.python} de mêmes dimensions. +- Vous appelez la méthode sur une `Series`{.python} et vous passez une `Series`{.python} de même dimension. + +Dans tous les cas, vous obtenez un `DataFrame`{.python} ou une `Series`{.python} dans le ou laquelle les valeurs +`NaN`{.python} sont remplacées par les valeurs à la même position dans le `DataFrame`{.python} ou la `Series`{.python} +passé en argument (les indexs et noms de colonne doivent correspondre). + +---- + +```python {.numberLines} +import pandas as pd +from numpy import nan + +df = pd.DataFrame(data={ + "siret": ["5001234567890", nan, "7001234567890", "800123456789"], + "nom": ["SARL Marks", "SAS Pareil", "SASU Key", nan], + "siege": ["Rue des roses", "Rue des baies", nan, nan] +}) + +df_fill = pd.DataFrame(data={ + "siret": ["14", "14", "14", "14"], + "nom": ["A", "A", "A", "A"], + "siege": ["B", "B", "B", "B"] +}) + +# La première colonne contiendra un "14", etc. +print(df.fillna(df_fill)) +``` + +---- + +## Modifier les informations d'un `DataFrame`{.python} + +Ce sous-chapitre va décrire comment changer le contenu d'un `DataFrame`{.python} (des fois en modifiant directement le contenu, +des fois en récupérant un résultat sous la forme d'un nouveau `DataFrame`{.python}). + +---- + +### Remplacer une colonne ou une ligne + +Cette opération modifie le contenu d'un `DataFrame`{.python} et utilise la syntaxe de Python pour manipuler les dictionnaires. +Cela donne par exemple : + +```python {.numberLines} +import pandas as pd + +df = pd.DataFrame(data={ + "prenom": ["Gisèle", "Jocelyne", "Charles", "Valentin"], + "nom": ["Garnier", "Jones", "Chancel", "Verdier"], + "numero": [8, 31, 23, 14] +}) + +# Changer la colonne `prenom` +# La valeur peut être une séquence ou un littéral +df["prenom"] = ["Aude", "Maud", "Raoul", "Gilles"] +# Changer la ligne à l'index 0 +# La valeur peut être une séquence, un dictionnaire ou un littéral +df.loc[0] = ["Arthur", "Martin", 1] +print(df) +``` + +---- + +### Créer une ligne ou une colonne + +Cette opération est très simple, il suffit juste d'utiliser des valeurs d'index de ligne +ou de colonne qui n'existent pas déjà pour créer un nouveau contenu (de la même manière +qu'avec un dictionnaire Python) + +```python {.numberLines} +import pandas as pd + +df = pd.DataFrame(data={ + "prenom": ["Gisèle", "Jocelyne", "Charles", "Valentin"], + "nom": ["Garnier", "Jones", "Chancel", "Verdier"], + "numero": [8, 31, 23, 14] +}) + +df["naissance"] = [1971, 1994, 2003, 1986] +df.loc[len(df.index)] = {"prenom": "Éric", "nom": "André", "numero": 19, "naissance": 1991} +print(df) +``` + +TODO: df.Append + +---- + +### Définir la valeur d'une cellule + +Il existe une façon recommandée d'accéder à, et surtout de modifier une cellule d'un `DataFrame`{.python} avec Pandas. +Il faut passer par l'attribut `.at`{.python} du `DataFrame`{.python} et l'utiliser comme un dictionnaire pour retrouver +une cellule à un index et une colonne donnée : + +```python {.numberLines} +import pandas as pd + +df = pd.DataFrame(data={ + "prenom": ["Gisèle", "Jocelyne", "Charles", "Valentin"], + "nom": ["Garnier", "Jones", "Chancel", "Verdier"], + "numero": [8, 31, 23, 14] +}) + +# Modifier la valeur à la ligne 2, colonne "nom" +df.at[2, "nom"] = "Courcelles" +print(df) +``` + +---- + +### Retirer une ligne ou une colonne + +Une façon recommandée de "corriger" un `DataFrame`{.python} en retirant soit +une ligne, soit une colonne (ou plusieurs) consiste à utiliser une méthode +`.drop()`{.python}. + +```python {.numberLines} +import pandas as pd + +df = pd.DataFrame(data={ + "prenom": ["Gisèle", "Jocelyne", "Charles", "Valentin"], + "nom": ["Garnier", "Jones", "Chancel", "Verdier"], + "numero": [8, 31, 23, 14] +}) + +# Retirer la colonne prénom et directement modifier le DataFrame +df.drop(labels=["prenom"], axis="columns", inplace=True) +# Retirer les lignes aux index 0 et 2, mais retourner un nouveau DataFrame +df2 = df.drop(labels=[0, 2], axis="index") +``` + +---- + +### Appliquer une fonction sur un `DataFrame`{.python} ou `Series`{.python} + +Vous pouvez appliquer un calcul personnalisé à tous les éléments d'une `Series`{.python} +ou d'un `DataFrame`{.python}, et récupérer respectivement une `Series`{.python} ou +un `DataFrame`{.python}. + +Le principe peut s'apparenter aux formules dans un document type Excel, +mais dans le cas courant, un calcul est executé une fois sur chaque élément d'une sélection, +et on récupère un `DataFrame`{.python} et une `Series`{.python} en résultat. + +```python {.numberLines} +import pandas as pd + +def double(s: pd.Series) -> pd.Series: + return s * 2 # Cela marche aussi sur les chaînes + +df = pd.DataFrame(data={ + "prenom": ["Gisèle", "Jocelyne", "Charles", "Valentin"], + "nom": ["Garnier", "Jones", "Chancel", "Verdier"], + "numero": [8, 31, 23, 14] +}) + +# Appliquer une fonction sur chaque colonne +df2 = df.apply(double, axis="columns") +``` + +---- + +L'exemple précédent peut être répliqué avec une simple fonction anonyme (`lambda`{.python}) : + +```python {.numberLines} +import pandas as pd + +df = pd.DataFrame(data={ + "prenom": ["Gisèle", "Jocelyne", "Charles", "Valentin"], + "nom": ["Garnier", "Jones", "Chancel", "Verdier"], + "numero": [8, 31, 23, 14] +}) + +# Appliquer une fonction sur chaque colonne +df2 = df.apply(lambda s: s * 2, axis="columns") +``` + +---- + +# Équivalents au SQL pour des `DataFrame`{.python} + +---- + +## Éléments SQL + +Ce chapitre va aborder des fonctionnalités dans Pandas qui reprennent des +concepts trouvables en bases de données SQL, nommément : + +1. les jointures (`JOIN`{.sql}) +2. les groupements (`GROUP BY`{.sql}) et +3. les fonctions de fenêtrage (`OVER`{.sql}). + +---- + +## Fusion (jointures) + +En SQL, une jointure consiste à faire correspondre des données appartenant à plusieurs tables pour obtenir une vue plus complète, +dans une seule table de résultat. Prenons comme exemple, une liste de transactions (virements) effectués au bénéfice de personnes +diverses. + +Notre base de données pourrait contenir les deux tables suivantes : + +![Tables de base](assets/images/structures-dataframe-merge-1.png) + +---- + +Ce que nous souhaitons obtenir, comme en SQL, est une table de résultat contenant les informations tirées +de la liaison entre la table `transactions` et la table `people`, avec un `INNER JOIN`{.sql} : + +![Table de fusion](assets/images/structures-dataframe-merge-inner.png) + +---- + +Si nous représentons les deux tables dans un code Python tel que le suivant : + +```python {.numberLines} +import pandas as pd + +transactions = pd.DataFrame(data={ + "id": [1, 2, 3, 4, 5, 6], + "date": ["2024-01-01", "2024-01-08", "2024-01-09", "2024-01-23", "2024-01-27", "2024-02-03"], + "amount": [150, 70, 86, 300, 75, 66], + "person": [1, 2, 1, 3, 2, 5], +}) +transactions["date"] = pd.to_datetime(transactions["date"]) + +people = pd.DataFrame(data={ + "id": [1, 2, 3, 4], + "firstname": ["Henri", "Colette", "Martine", "Jean-Pierre"], + "lastname": ["Bouvier", "Chaland", "Duvivier", "Escudé"], +}) +``` + +---- + +Nous pouvons obtenir la table de fusion avec la méthode `.merge()`{.python}. +Par défaut, vous obtiendrez le même résultat qu'un `INNER JOIN`{.sql} en SQL : + +```python {.numberLines} +import pandas as pd + +transactions = pd.DataFrame(...) +people = pd.DataFrame(...) + +# Récupérer la fusion avec people où transactions.person = people.id +cartesian_product = transactions.merge(people, left_on="person", right_on="id") +``` + +Seules les lignes de `transactions`{.python} ayant un équivalent dans `people`{.python} +seront présentes dans le résultat. + +---- + +### Autres stratégies de fusion + +En **SQL**, les jointures peuvent se faire d'au moins quatre façons : + +- `INNER JOIN`{.sql} : correspondance des deux côtés +- `LEFT JOIN`{.sql} : toutes les lignes de gauche apparaissent +- `RIGHT JOIN`{.sql} : toutes les lignes de droite apparaissent +- `OUTER JOIN`{.sql} : toutes les lignes apparaissent + +Toutes ces stratégies sont applicables à la méthode `.merge()`{.python} des `DataFrame`{.python}, via +l'argument `how`{.python}. + +Les valeurs possibles de cet argument sont : + +- `how="inner"`{.python} +- `how="outer"`{.python} +- `how="left"`{.python} +- `how="right"`{.python} +- `how="cross"`{.python} : produit cartésien, pas une jointure + +---- + +## Groupes d'agrégation (GROUP BY) + +En SQL, la notion de groupements consiste à considérer un jeu de données, +et indiquer au moteur de base de données que ledit jeu doit être découpé en groupes +afin de pouvoir appliquer une fonction d'agrégation à chaque groupe. + +Par exemple, si nous considérons le document Excel suivant : + +![Ventes](assets/images/structures-dataframe-groupby-table.png) + +---- + +Nous pouvons matérialiser l'exemple précédent par le code suivant : + +```python {.numberLines} +import pandas as pd + +sales = pd.DataFrame(data={ + "id": [1, 2, 3, 4, 5, 6, 7, 8], + "country": ["France", "France", "France", "France", "Spain", "Spain", "Spain", "Spain"], + "city": ["Lille", "Paris", "Paris", "Chartres", "Malaga", "Madrid", "Barcelona", "Barcelona"], + "product": ["wrench", "hammer", "screws", "drill", "hammer", "screws", "nails", "saw"], + "sales": [200, 650, 100, 315, 250, 90, 400, 440] +}) +``` + +---- + +Si nous désirons ensuite calculer la somme des ventes par pays : + +![Ventes par pays](assets/images/structures-dataframe-groupby-groups-country.png) + +---- + +Nous pouvons le faire grâce à la méthode `.groupby()`{.python} : + +```python {.numberLines} +import pandas as pd + +sales = pd.DataFrame(data={ + "id": [1, 2, 3, 4, 5, 6, 7, 8], + "country": ["France", "France", "France", "France", "Spain", "Spain", "Spain", "Spain"], + "city": ["Lille", "Paris", "Paris", "Chartres", "Malaga", "Madrid", "Barcelona", "Barcelona"], + "product": ["wrench", "hammer", "screws", "drill", "hammer", "screws", "nails", "saw"], + "sales": [200, 650, 100, 315, 250, 90, 400, 440] +}) +# Les groupes sont uniquement utilisables pour des agrégations +# Tenter une agrégation sur toutes les colonnes numériques +sales_by_country = sales.groupby("country").sum(numeric_only=True) +# Tenter une agrégation sur les colonnes qui m'intéressent +sales_by_country = sales.groupby("country")[["sales", "id"]].sum(numeric_only=True) +``` + +---- + +## Fonctions de fenêtrage (`OVER`{.sql}) + +En SQL, le fenêtrage permet d'utiliser des fonctions d'agrégation sur toutes les lignes d'une table. +Il est possible d'appliquer une fonction qui calcule pour chaque ligne la somme d'une de +ses colonnes, mais uniquement à partir des deux lignes précédentes, ex. : + +![Données de fenêtrage](assets/images/structures-dataframe-windowing-data.png) + +---- + +### Fonctions d'agrégation mobiles/glissantes + +Si nous appliquons cette idée sur la colonne `minutes`, nous obtiendrons une colonne avec les valeurs suivantes : +`31`{.python}, `31 + 47 == 78`{.python}, `31 + 47 + 26 == 104`{.python}, `47 + 26 + 52 == 125`{.python}, `26 + 52 + 34 == 102`{.python}, `52 + 34 + 22 == 108`{.python}, etc… + +```python {.numberLines} +import pandas as pd + +runs = pd.DataFrame(data={ + "id": [1, 2, 3, 4, 5, 6, 7, 8, 9], + "date": pd.date_range("2024-01-01", "2024-01-09", periods=9), + "minutes": [31, 47, 26, 52, 34, 22, 36, 15, 30], +}) +# Calculer la nouvelle colonne +runs["3-day sum"] = runs["minutes"].rolling(2, min_periods=0, center=False, closed="both").sum() +``` + +---- + +### Fonctions d'agrégation mobiles cumulatives + +Nous pouvons appliquer la même stratégie pour calculer la somme depuis la première ligne lorsque +nous appliquons la somme mobile à chaque ligne du `DataFrame`{.python} : + +```python {.numberLines} +import pandas as pd + +runs = pd.DataFrame(data={ + "id": [1, 2, 3, 4, 5, 6, 7, 8, 9], + "date": pd.date_range("2024-01-01", "2024-01-09", periods=9), + "minutes": [31, 47, 26, 52, 34, 22, 36, 15, 30], +}) +# Calculer la nouvelle colonne +runs["total sum"] = runs["minutes"].expanding(min_periods=0).sum() +# La même chose peut être faite avec une somme roulante avec un grand nombre d'observations +runs["total sum+"] = runs["minutes"].rolling(len(runs), min_periods=0).sum() +``` + +Les méthodes `runs.rolling`{.python} et `runs.expanding`{.python} peuvent être précédées d'un +`.groupby()`{.python} et d'un `.sort_values()`{.python} pour créer des groupes d'agrégation +et organiser les éléments dans chaque groupe. diff --git a/documentation/new-01.6-pandas-config.md b/documentation/new-01.6-pandas-config.md new file mode 100644 index 0000000..bacb819 --- /dev/null +++ b/documentation/new-01.6-pandas-config.md @@ -0,0 +1,136 @@ +--- +title: Pandas +author: Steve Kossouho +--- + +# Configurer Pandas + +---- + +## Personnaliser l'affichage d'un `DataFrame` + +Par défaut, dans Pandas, afficher un `DataFrame`{.python} fonctionne très bien sur des +petits jeux de données, typiquement des objets avec quelques colonnes et quelques lignes. + +Cependant, Pandas est capable de s'ajuster, et lorsque vous devez afficher des jeux +de données plus conséquents (ex. des dizaines de colonnes ou des milliers de lignes), +vous allez généralement observer un affichage tronqué. + +---- + +### Affichage d'un jeu de données de Seaborn + +```python {.numberLines} +import pandas as pd + +# Le jeu de données Iris de Seaborn est disponible directement sur Github +iris = pd.read_csv('https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv') +print(iris) +``` + +Notez dans cet exemple que malgré la présence de 150 lignes, Pandas n'a affiché que 10 +d'entre elles, à savoir les 5 premières et les 5 dernières. + +---- + +Il est possible d'afficher l'intégralité du `DataFrame`{.python} en utilisant une méthode +`.to_string(...)`{.python}, qui exporte par défaut le contenu intégral dans une chaîne de +caractères : + +```python {.numberLines} +import pandas as pd + +# Le jeu de données Iris de Seaborn est disponible directement sur Github +iris = pd.read_csv('https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv') +print(iris.to_string()) +``` + +---- + +### La méthode `to_string()`{.python} + +Cette méthode, disponible sur les `DataFrame`{.python} et les `Series`{.python}, extrait +par défaut l'intégralité du document vers une chaîne de caractères, et est configurable +via un certain nombre d'arguments : + +[Documentation de `DataFrame.to_string()`{.python}](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.to_string.html) + +---- + +La liste des arguments intéressants est la suivante : + +| Argument | Description | Défaut | +|-------------------|-------------------------------------------------------------------------------------------------------|--------| +| `columns` | Liste des colonnes à inclure dans la sortie. | `None` | +| `col_space` | Largeur minimale de chaque colonne. | `None` | +| `header` | Écrire les noms des colonnes. | `True` | +| `index` | Afficher l'index (étiquettes de ligne). | `True` | +| `index_names` | Afficher les noms des index. | `True` | +| `max_rows` | Nombre maximum de lignes à afficher. Si `None`, afficher toutes. | `None` | +| `min_rows` | Nombre de lignes à afficher dans une représentation tronquée. | `None` | + +---- + +| Argument | Description | Défaut | +|-------------------|-------------------------------------------------------------------------------------------------------|---------| +| `max_cols` | Nombre maximum de colonnes à afficher. Si `None`, afficher toutes. | `None` | +| `show_dimensions` | Afficher les dimensions du DataFrame (nombre de lignes par nombre de colonnes) à la fin du DataFrame. | `False` | +| `line_width` | Largeur de ligne en caractères pour effectuer un retour à la ligne. | `None` | +| `max_colwidth` | Largeur maximale de colonne pour tronquer le texte des colonnes individuelles. | `None` | + +---- + +## Personnaliser l'affichage par défaut + +Utiliser la méthode `.to_string()`{.python} à chaque affichage de `DataFrame`{.python} peut vite devenir +rébarbatif, surtout si vous précisez souvent de nombreux arguments. Pandas possède un objet de configuration globale +qui permet de définir le comportement par défaut de la bibliothèque, notamment en ce qui concerne l'affichage. + +---- + +### L'objet `options`{.python} + +Pandas propose une fonction nommée `set_option()`{.python} pour définir des paramètres de fonctionnement +de la bibliothèque. On préférera directement travailler avec une variable `options`{.python} qui propose +des attributs et de l'autocomplétion dans votre éditeur préféré (via le package `pandas-stubs`). + +```python {.numberLines} +import pandas as pd + +pd.options.display.width = 160 # caractères max par ligne +pd.options.display.max_colwidth = 20 # max par colonne +... +``` + +[Options disponibles](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.set_option.html#pandas.set_option) + +---- + +### Options disponibles + +| Attribut de l'option | Explication succincte | +|-----------------------------|----------------------------------------------------------------| +| `display.max_rows` | Nombre maximum de lignes à afficher | +| `display.max_columns` | Nombre maximum de colonnes à afficher | +| `display.width` | Largeur maximale de l'affichage en caractères | +| `display.max_colwidth` | Largeur maximale en caractères pour une colonne | +| `display.precision` | Nombre de décimales à afficher pour les nombres | +| `display.float_format` | Format des nombres à virgule flottante | +| `display.max_seq_items` | Nombre maximum d'éléments d'une séquence à afficher | +| `display.expand_frame_repr` | Expansion automatique des DataFrames pour remplir la largeur | +| `display.colheader_justify` | Justification des en-têtes de colonnes (left, right, center) | + +---- + +| Attribut de l'option | Explication succincte | +|-----------------------------|----------------------------------------------------------------| +| `display.show_dimensions` | Afficher les dimensions du DataFrame (True, False, 'truncate') | +| `display.memory_usage` | Afficher l'utilisation de la mémoire pour les DataFrames | +| `display.large_repr` | Représentation des grandes DataFrames ('truncate', 'info') | +| `display.notebook_repr_html` | Utiliser l'HTML pour la représentation dans les notebooks | +| `display.html.border` | Largeur de la bordure en pixels pour les représentations HTML | +| `display.min_rows` | Nombre minimum de lignes à afficher | +| `display.max_info_columns` | Nombre maximum de colonnes à inclure dans la sortie d'info() | +| `display.max_info_rows` | Nombre maximum de lignes à inclure dans la sortie d'info() | +| `display.memory_usage` | Afficher l'utilisation de la mémoire (True, False, 'deep') | + diff --git a/documentation/new-01.7-pandas-files.md b/documentation/new-01.7-pandas-files.md new file mode 100644 index 0000000..b992437 --- /dev/null +++ b/documentation/new-01.7-pandas-files.md @@ -0,0 +1,222 @@ +--- +title: Pandas +author: Steve Kossouho +--- + +# Manipuler des fichiers + +---- + +## Charger un fichier + +Si le principal intérêt de Pandas est de manipuler des données pour en extraire des informations, cela +se fait difficilement sans outils intégrés pour charger des sources de données généralistes. + +Pandas est capable de lire et de convertir en `DataFrame`{.python} des données provenant de sources dans +des formats tels que [CSV]{.naming}, [JSON]{.naming}, [XLSX]{.naming}, [SAS]{.naming} ou encore des requêtes [SQL]{.naming}. + +---- + +### Fichiers CSV + +La fonction de chargement de documents CSV de Pandas se nomme simplement `pandas.read_csv()`{.python}. +Cette fonction est capable de renvoyer un `DataFrame`{.python} depuis un document, dont le chemin peut être : + +1. Un chemin de fichier local, absolu ou relatif; +2. une URL utilisant le protocole HTTP, HTTPS, FTP ou S3. + +```python {.numberLines} +import pandas as pd + +data = pd.read_csv("https://sub.domain.tld/resource-path.csv") +``` + +La fonction propose de nombreux _arguments avec valeur par défaut_ pour personnaliser le chargement du +document. Il est ainsi possible de personnaliser l'encodage, la détection d'en-tête, ou encore demander à +interpréter spécifiquement le contenu de colonnes de type date ou de type numérique. + +[Documentation de `read_csv`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_csv.html) + +---- + +### Fichiers Excel + +Les documents [Open Document (ODS)]{.naming} et [Excel]{.naming} possèdent plusieurs +différences notables avec les documents [CSV]{.naming} : + +1. Ils contiennent potentiellement plusieurs feuilles de calcul; +2. ils possèdent des informations sur le type ([format]{.naming}) des cellules. + +Vous pouvez ainsi charger plusieurs `DataFrame`{.python} depuis un seul document de tableur, +ou choisir une feuille de calcul à charger. Également, vous n'avez normalement pas à préciser +d'arguments pour interpréter correctement des dates; l'information est présente dans le document. + +[Documentation de `read_excel`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_excel.html) + +```python {.numberLines} +import pandas as pd + +data: pd.DataFrame = pd.read_excel("https://sub.domain.tld/resource-path.csv", sheet_name="Sheet1") +sheets: dict[str, pd.DataFrame] = pd.read_excel("https://sub.domain.tld/resource-path.csv", sheet_name=None) +``` + +---- + +#### Bibliothèques tableur + +Selon le type de document à charger, par défaut, Pandas aura besoin d'une bibliothèque +supplémentaire pour interpréter le contenu du document. + +| Type de document | Bibliothèque à installer | +|------------------|-------------------------------| +| `.odf` | `pip install odfpy`{.bash} | +| `.xls` | `pip install xlrd`{.bash} | +| `.xlsb` | `pip install pyxlsb`{.bash} | +| `.xlsx` | `pip install openpyxl`{.bash} | + +---- + +### Requête SQL + +Bien que le SQL puisse être utilisé seul pour de l'analyse de données, charger le +résultat d'une requête SQL dans un `DataFrame`{.python} peut être utile pour +effectuer des calculs spécifiques avec Pandas, ou encore générer facilement des +graphiques. + +Pandas propose deux fonctions pour charger du contenu SQL : + +1. `def read_sql_table(table: str, connection, ...)`{.python} +2. `def read_sql_query(sql: str, connection, ...)`{.python} + +- [Documentation de `read_sql_table`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_sql_table.html) +- [Documentation de `read_sql_query`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_sql_query.html) + +---- + +Ces deux fonctions nécessitent un objet de connexion à une base, qui peut +provenir de deux bibliothèques uniquement : + +| Base de données | Bibliothèque à utiliser | +|----------------------|---------------------------| +| SQLite3 | `sqlite3` ou `sqlalchemy` | +| MySQL / MariaDB | `sqlalchemy` | +| PostgreSQL | `sqlalchemy` | +| Oracle | `sqlalchemy` | +| Microsoft SQL Server | `sqlalchemy` | + +---- + +#### SQLAlchemy + +[SQLAlchemy](https://www.sqlalchemy.org/) est une bibliothèque qu'on appelle communément +un [ORM]{.naming}. C'est une abstraction objet qui permet de converser avec des moteurs de +base de données SQL uniquement avec des classes et des méthodes Python. + +Pandas est capable d'utiliser des objets de connexion de la bibliothèque SQLAlchemy pour +envoyer des commandes brutes SQL. + + +```python {.numberLines} +import pandas as pd +from sqlalchemy import create_engine + +# L'URL dépend du moteur à utiliser. +engine = create_engine("sqlite:///home/user/path/file.db") +with engine.connect() as connection: + data = pd.read_sql_table("", connection) +``` + +---- + +#### SQLite3 + +La bibliothèque SQLite3 fournie avec les distributions classiques de Python, est +une bibliothèque [DBAPI 2.0]{.naming} uniquement compatible avec le moteur de bases +de données embarquées SQLite 3. + +Pandas accepte les objets de connexion de la bibliothèque pour envoyer des commandes +SQL. + +```python {.numberLines} +import pandas as pd +import sqlite3 + +connection = sqlite3.connect("/home/user/path/file.db") +data = pd.read_sql_query("SELECT * FROM table WHERE column = 1", connection) +connection.close() +``` + +---- + +### SAS + +Les fichiers de [datasets]{.naming} produits par la solution commerciale propriétaire SAS +sont lisibles par Pandas. Le type des colonnes récupérées correspond normalement à ce qui +aura été défini dans le document. + +```python {.numberLines} +import pandas as pd + +data = pd.read_sas("/path/to/file.sas7bdat") +``` + + +---- + +### Parquet + +Le format [Parquet]{.naming} est un format de fichier en colonnes optimisé pour une utilisation avec des systèmes de +traitement de données à grande échelle comme Hadoop. Il est spécialement conçu pour fonctionner avec la bibliothèque +Apache Arrow et fournit des performances exceptionnelles pour des ensembles de données de grande taille. + +Pour charger un DataFrame depuis un fichier Parquet, vous pouvez utiliser la fonction `pandas.read_parquet()`{.python}. +Cette fonction renvoie un objet `DataFrame` à partir d'une source de fichier Parquet. +La source peut être un chemin de fichier local, absolu ou relatif ou une URL utilisant le protocole HTTP, FTP ou S3. + +Assurez-vous d'abord d'installer le paquet nécessaire pour lire les fichiers Parquet : + +```bash {.numberLines} +pip install pyarrow # ou pip install fastparquet +``` + +---- + +Voici un exemple de code pour charger un DataFrame à partir d'un fichier Parquet : + +```python {.numberLines} +import pandas as pd + +# Charger un DataFrame depuis un fichier Parquet +df = pd.read_parquet('/path/to/fichier.parquet') +``` + +Notez que si votre fichier Parquet est compressé (par exemple avec Snappy, Gzip ou Brotli), +Pandas sera en mesure de le décompresser automatiquement lors de la lecture. + +---- + +## Sauvegarder pour Excel + +Enregistrer un fichier Excel depuis les données d'un `DataFrame` est extrêmement simple; +chaque `DataFrame`{.python} possède des méthodes de sérialisation telles que +`.to_excel()`{.python} + +```python {.numberLines} +import pandas as pd + +# Créer un dataframe simple +df = pd.DataFrame(data={"A": [1, 2], "B": ["Bonjour", "Hello"]}) +# Enregistrer le fichier avec les données du DataFrame +df.to_excel("nom du fichier.xlsx") +``` + +Le fichier possèdera un style de tableur de base, qui ne semble à l'heure actuelle actuelle pas +très configurable. + +---- + +## Exemples de ressources fichier + +- [Fichiers au format Parquet](https://github.com/Teradata/kylo/tree/master/samples/sample-data/parquet) +- [Fichiers au format CSV](https://www.datablist.com/learn/csv/download-sample-csv-files) +- [Fichiers au format Excel 2007+](https://file-examples.com/index.php/sample-documents-download/sample-xls-download/) diff --git a/documentation/new-01.8-scikit-learn-intro.md b/documentation/new-01.8-scikit-learn-intro.md new file mode 100644 index 0000000..3c6ab0c --- /dev/null +++ b/documentation/new-01.8-scikit-learn-intro.md @@ -0,0 +1,32 @@ +--- +title: Scikit-Learn +author: Steve Kossouho +--- + +# Scikit-Learn + +Scikit-Learn est une bibliothèque Python qui fournit des outils d'apprentissage, de classification et de prédiction. +La bibliothèque est destineée à des professionnels qui ont besoin de des outils de machine learning. +Elle implémente de nombreux algorithmes mathématiques adaptables à de nombreux besoins de traitements. + +![Logo de Scikit-Learn](assets/images/scikit-library-logo.png) + +---- + +## Régressions + +### Régressions lineaires + +Scikit-Learn fournit des outils pour les régressions lineaires, qui sont la première chose que l'on peut faire avec Python. + +```python {.numberLines} +from sklearn.linear_model import LinearRegression + +model = LinearRegression() +xs = [[0, 1], [1, 1], [2, 1]] +ys = [0, 1, 2] +coeff: LinearRegression = model.fit(xs, ys) + +# L'ordonnée des points est x +print(coeff.coef_) +``` diff --git a/documentation/new-02.1-eda-matplotlib.md b/documentation/new-02.1-eda-matplotlib.md new file mode 100644 index 0000000..dfe22bc --- /dev/null +++ b/documentation/new-02.1-eda-matplotlib.md @@ -0,0 +1,208 @@ +--- +title: Matplotlib +author: Steve Kossouho +--- + +# Rendu de graphiques Matplotlib + +---- + +## Qu'est-ce que Matplotlib ? + +Matplotlib, selon ses créateurs (John D. Hunter et al.), est une bibliothèque complète, +pour créer des graphiques statiques, animés ou interactifs 👀. + +La bibliothèque rend des choses simples très simples, +et des choses compliquées possibles +(_et accessoirement beaucoup de choses simples compliquées_). + +---- + +## Comment fonctionne Matplotlib ? + +La bibliothèque permet de dessiner des graphiques, souvent très simplement en +assemblant des éléments, par exemple un axe avec une certaine échelle, et +une ligne suivant la même échelle. + +Les possibilités sont très nombreuses, et les concepts proposés aussi, avec une +approche très différente de ce qui se fait normalement en Python; usage de variables +globales ou classes complexes pour représenter de simples dégradés de couleurs. + +---- + +## Graphiques avec Pandas + +---- + +La bibliothèque Matplotlib permet facilement de générer et afficher +des graphiques, mais les objets `DataFrame`{.python} proposent un +raccourci pour les générer facilement. + +Chaque objet possède une méthode générale `.plot()`{.python}, qui possède +des attributs qui sont des méthodes spécifiques pour chaque type +de graphique. + +---- + +| Méthode | Explication succincte | +|----------------------|----------------------------------------------| +| [`plot.area`][1] | Tracer un graphique en _aires empilées_ | +| [`plot.bar`][2] | Tracer un graphique en _barres verticales_ | +| [`plot.barh`][3] | Tracer un graphique en _barres horizontales_ | +| [`plot.box`][4] | Tracer un graphique à _moustaches_ (boxplot) | +| [`plot.density`][5] | Tracer une estimation de densité de noyau | +| [`plot.hexbin`][6] | Tracer un graphique hexagonal en 2D | +| [`plot.hist`][7] | Tracer un _histogramme_ | +| [`plot.kde`][8] | Tracer une estimation de densité de noyau | +| [`plot.line`][9] | Tracer un graphique en _lignes_ | +| [`plot.pie`][10] | Tracer un graphique en _secteurs_ | +| [`plot.scatter`][11] | Tracer un _nuage de points_ (scatter plot) | + +[1]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.plot.area.html +[2]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.plot.bar.html +[3]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.plot.barh.html +[4]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.plot.box.html +[5]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.plot.density.html +[6]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.plot.hexbin.html +[7]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.plot.hist.html +[8]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.plot.kde.html +[9]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.plot.line.html +[10]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.plot.pie.html +[11]: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.plot.scatter.html + + +---- + +### Barres + +```python {.numberLines} +import pandas as pd +from matplotlib import use, pyplot + +# Utiliser tkinter +use("TkAgg") + +# Données à afficher +data = pd.DataFrame(data={ + "product": ["pomme", "poire", "banane", "pêche"], + "price": [1.99, 2.49, 2.99, 3.49] +}) + +# Générer un graphique dans Matplotlib +axis = data.plot.bar(x="product", y="price") +# Afficher le dernier graphique généré +pyplot.show() +``` + +[`DataFrame.plot.bar`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.plot.bar.html#pandas.DataFrame.plot.bar) + +---- + +![Rendu de base du graphique en barres](assets/images/eda-matplotlib-bar-default.png) + +Notez que les étiquettes des barres, entre autres, débordent de l'image. D'autres défauts généraux +sont habituellement rencontrés avec Matplotlib, comme l'absence d'affichage de valeur pour les +éléments des graphiques, ou l'absence de gestion du contraste lorsqu'il faut afficher du texte sur +des fonds sombres ou clairs. + +---- + +### Personnalisation + +De nombreuses options de personnalisation permettent de changer le rendu par défaut des graphiques. +La variable `rcParams`{.python} de `pyplot`{.python} est un dictionnaire proposant des options +pour changer entre autres la police de texte par défaut ou les styles de ligne. + +```python {.numberLines} +import matplotlib +import pandas as pd +from matplotlib import pyplot + +matplotlib.use("TkAgg") +pyplot.style.use('ggplot') +pyplot.rcParams["font.family"] = "Cabin" + +data = pd.DataFrame(data={ + "product": ["pomme", "poire", "banane", "pêche"], + "price": [1.99, 2.49, 2.99, 3.49], + "wpu": [200, 180, 140, 200] +}) +# Générer un graphique dans Matplotlib +data.plot.bar(x="product", y=["price", "wpu"], rot=0.0, secondary_y="wpu", legend="reverse") +pyplot.gcf().savefig("test.png") +``` + +[Options de `rcParams`](https://matplotlib.org/stable/api/matplotlib_configuration_api.html#matplotlib.rcParams) + +---- + +![Rendu personnalisé du graphique en barres](assets/images/eda-matplotlib-bar-themed.png) + +---- + +#### Ajout d'étiquettes sur des barres + +```python {.numberLines} +import matplotlib +import pandas as pd +from matplotlib import pyplot +from matplotlib.axes import Axes + +matplotlib.use("TkAgg") +pyplot.style.use('ggplot') +pyplot.rcParams["font.family"] = "Cabin" + +data = pd.DataFrame(data={ + "product": ["pomme", "poire", "banane", "pêche"], + "price": [1.99, 2.49, 2.99, 3.49], + "wpu": [200, 180, 140, 200] +}) +# Générer un graphique dans Matplotlib +data.plot.bar(x="product", y=["price", "wpu"], rot=0.0, secondary_y="wpu", legend="reverse", title="Prix et poids unitaires") +prices, weights = pyplot.gcf().axes # type: Axes +prices.legend(bbox_to_anchor=(0.0, 1.1), loc="upper left") +weights.legend(bbox_to_anchor=(1.0, 1.1), loc="upper right") +# Il est difficile de personnaliser le contenu du graphique +options: dict = {"fontsize": 8, "color": "w", "rotation": 90, "label_type": "center"} +prices.bar_label(prices.containers[0], labels=[f"{p}€" for p in data["price"]], **options) +weights.bar_label(weights.containers[0], labels=[f"{p}g" for p in data["wpu"]], **options) +pyplot.gcf().savefig("bar-labeled.png") + +``` + +---- + +![Rendu personnalisé du graphique en barres](assets/images/eda-matplotlib-bar-labeled.png) + +Le travail nécessaire est peut-être trop élevé pour une visualisation simple. + +---- + +### Secteurs (camembert) + +```python {.numberLines} +import pandas as pd +from matplotlib import use, pyplot + +# Utiliser tkinter +use("TkAgg") +pyplot.style.use('ggplot') +pyplot.rcParams["font.family"] = "Cabin" + +# Données à afficher +data = pd.DataFrame(data={ + "product": ["pomme", "poire", "banane", "pêche"], + "price": [1.99, 2.49, 2.99, 3.49] +}) + +# Générer un graphique dans Matplotlib +axis = data.set_index("product").plot.pie(y="price", title="Prix") +# Afficher le dernier graphique généré +pyplot.show() +``` + +[`DataFrame.plot.pie`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.plot.pie.html#pandas.DataFrame.plot.pie) + +---- + +![Rendu personnalisé du graphique en secteurs](assets/images/eda-matplotlib-pie-themed.png) diff --git a/documentation/new-02.2-eda-plotly.md b/documentation/new-02.2-eda-plotly.md new file mode 100644 index 0000000..9aaf6fa --- /dev/null +++ b/documentation/new-02.2-eda-plotly.md @@ -0,0 +1,334 @@ +--- +title: Plotly Express +author: Steve Kossouho +--- + +# Rendu de graphiques avec Plotly + +---- + +## Qu'est-ce que Plotly ? + +Selon le site de Plotly : La bibliothèque graphique Python Plotly crée des graphiques interactifs de qualité +propre à la publication. + +![Logo de Plotly](assets/images/eda-plotly-logo.png) + +---- + +## Comment fonctionne Plotly ? + +Plotly est une bibliothèque Python compatible avec les `DataFrame`{.python} Pandas, qui permet de créer des graphiques +interactifs, et cela grâce aux technologies web (HTML, CSS et JS). Un navigateur suffit à +afficher le contenu d'un graphique généré avec Plotly. + +La bibliothèque est relativement facile à prendre en main et bien documentée, de sorte +qu'il est simple de faire des choses simples, et abordable de produire des choses plus +complexes. + +[Documentation de Plotly pour Python](https://plotly.com/python/) + +---- + +## Plotly Express + +Plotly express est une bibliothèque de Plotly proposant très simplement de +créer des graphiques, depuis des `DataFrame`{.python} Pandas. Elle propose +toute la variété de graphiques générables avec Plotly, via des fonctions prêtes +à l'emploi prenant en argument un `DataFrame`{.python}. + +---- + +### Installer Plotly Express + +```bash= {.numberLines} +pip install plotly +``` + +---- + +### Barres + +Générer un graphique en barres est assez simple avec Plotly, et fonctionne +de manière relativement satisfaisante par défaut (ex. texte en blanc sur fond sombre) + +```python {.numberLines} +import pandas as pd +from plotly import express + + +data = pd.DataFrame(data={ + "product": ["pomme", "poire", "banane", "pêche"], + "price": [1.99, 2.49, 2.99, 3.49], + "wpu": [200, 180, 140, 200] +}) +# Générer un graphique +figure = express.bar(data, x="product", y=["price", "wpu"], barmode="group", title="Prix et poids unitaires") +figure.show() +``` + +[Documentation de `bar`](https://plotly.com/python-api-reference/generated/plotly.express.bar.html) + +---- + +![Rendu de base en barres](assets/images/eda-plotly-express-bar-default.png) + +---- + +### Barres (sur un objet `Series`) + +Vous pouvez également générer un graphique en partant d'un objet de type `Series`, même si l'objet ne contient +en lui-même qu'une seule colonne. Les valeurs d'index peuvent également servir sur un axe; il suffit d'utiliser +la valeur `None`{.python} : + +```python {.numberLines} +import pandas as pd +from plotly import express + + +values = pd.Series( + data=[1.99, 2.49, 2.99, 3.49], + index=["pomme", "poire", "banane", "peche"], + name="price" +) +# Générer un graphique +figure = express.bar(values, x=None, y="price") +figure.show() +``` + +L'axe des Y pourra utiliser le nom associé à l'objet `Series` (`values.name`{.python}) + +---- + +Quelques personnalisations sont possibles, notamment via la notion de `template` (thème), +et de `layout` (organisation du contenu). + +```python {.numberLines} +import pandas as pd +from plotly import express + +data = pd.DataFrame(data={ + "product": ["pomme", "poire", "banane", "pêche"], + "price": [1.99, 2.49, 2.99, 3.49], + "wpu": [200, 180, 140, 200] +}) +# Générer un graphique. Plotly express ne prend en charge qu'un seul graphique par figure +figure = express.bar(data, x="product", y="price", title="Prix", template="seaborn", + labels={"price": "Prix", "product": "Produit"}) +figure.layout.update({"font": {"family": "Cabin", "size": 13}}) +figure.show() +``` + +---- + +Voici une liste des valeurs possibles fournies par Plotly pour l'argument `template`{.python}: + +- `ggplot2` : Thème par défaut de GGPlot2 pour [R]{.naming} +- `seaborn` : Thème par défaut de Seaborn pour Python +- `plotly` : Thème par défaut de Plotly +- `plotly_white` +- `plotly_dark` +- `presentation` +- `xgridoff` +- `ygridoff` +- `gridon` +- `none` + +---- + +![Rendu personnalisé en barres](assets/images/eda-plotly-express-bar-themed.png) + +---- + +![Rendu personnalisé en barres 2](assets/images/eda-plotly-express-bar-custom.png) + +---- + +### Secteurs concentriques + +Le graphique [sunburst]{.naming} permet d'afficher des secteurs, et de les découper en sous-catégories +dans des anneaux concentriques. Ici, nous avons un `DataFrame`{.python} avec des ventes par ville, +chaque ville appartenant à un pays. + +```python {.numberLines} +import pandas as pd +import plotly +from plotly.express import sunburst + +if __name__ == '__main__': + df = pd.DataFrame(data={ + "country": ["France", "France", "Spain", "Spain", "England", "England", "England"], + "city": ["Montpellier", "Bordeaux", "Madrid", "Valencia", "London", "Manchester", "Bristol"], + "sales": [150_000, 127_000, 97_200, 137_250, 200_000, 180_000, 150_000] + }) + plot = sunburst(df, path=["country", "city"], values="sales", title="Sales by country and city", template="ggplot2", + color_discrete_sequence=plotly.colors.qualitative.Dark2) + plot.layout.update({"font": {"family": "Cabin", "size": 13}}) + plot.show() +``` + +[Documentation de `sunburst`](https://plotly.com/python-api-reference/generated/plotly.express.sunburst.html) + +---- + +![Rendu personnalisé en [sunburst]{.naming}](assets/images/eda-plotly-express-sunburst-themed.png) + +---- + +### Nuages de points + +Les nuages de points ([scatter plot]{.naming}) sont un moyen pratique de mettre en regard deux valeurs +d'une même observation afin d'essayer de déduire une tendance ou une corrélation. Ils se présentent en +2 dimensions, mais deux autres axes peuvent être représentés via la taille ou la couleur des points du +graphique. + +```python {.numberLines} +import pandas as pd +from plotly import express as px + +data = pd.read_csv("https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data", header=None, + names=["sepal-length", "sepal-width", "petal-length", "petal-width", "class"]) + +plot = px.scatter(data, x="sepal-length", y="sepal-width", size="petal-width", color="class", template="seaborn", + title="Iris flowers dataset", + labels={"sepal-length": "Sepal length", "sepal-width": "Sepal width", "petal-width": "Petal width", "class": "Class"}) +plot.layout.update({"font": {"family": "Cabin", "size": 13}}) +plot.show() +``` + +[Documentation de `scatter`](https://plotly.com/python-api-reference/generated/plotly.express.scatter.html) + +---- + +![Rendu personnalisé en [scatter]{.naming}](assets/images/eda-plotly-express-scatter-themed.png) + +---- + +## Construction + +Cette section regroupe plusieurs solutions pour parvenir à certains résultats. + +---- + +### Deux axes Y dans un graphique en barres + +Par défaut, Plotly Express ne génère pas deux axes distincts lorsque vous affichez un graphique +avec deux colonnes pour l'axe Y. Si leurs ordres de grandeur sont différents, les barres partageront toutefois +la même échelle. + +Pour obtenir deux axes distincts en Y, nous devons utiliser Plotly manuellement. Rien d'anormalement +complexe toutefois : + +```python {.numberLines} +import pandas as pd +from plotly.graph_objs import Figure, Bar + +data = pd.DataFrame(data={ + "product": ["pomme", "poire", "banane", "pêche"], + "price": [1.99, 2.49, 2.99, 3.49], "wpu": [200, 180, 140, 200] +}) +figure = Figure([ + Bar(name="Prix", x=data["product"], y=data["price"], yaxis="y", offsetgroup=1), + Bar(name="Poids", x=data["product"], y=data["wpu"], yaxis="y2", offsetgroup=2), +]) +# Afficher le dernier graphique généré +figure.layout.update({ + "template": "seaborn", + "title": "Prix et poids unitaires", "font": {"family": "Cabin", "size": 13}, + "yaxis": {"title": "Prix (€)"}, "yaxis2": {"title": "Poids (g)", "overlaying": "y", "side": "right"} +}) +figure.show() +``` + +---- + +![Rendu avec deux axes Y](assets/images/eda-plotly-compose-2axes.png) + +---- + +### Texte personnalisé dans un camembert + +Par défaut, les secteurs de graphique camembert ([pie]{.naming}) affichent la taille +d'un secteur en pourcents du total. Vous pouvez y formater d'autres infos : + +```python {.numberLines} +import pandas as pd +from plotly import express as px + +data = pd.DataFrame(data={ + "product": ["pomme", "poire", "banane", "pêche"], + "price": [1.99, 2.49, 2.99, 3.49], "wpu": [200, 180, 140, 200] +}) +figure = px.pie(data, values="price", names="product", title="Prix") +# Afficher le dernier graphique généré +figure.layout.update({ + "template": "seaborn", + "title": "Prix au kilo", "font": {"family": "Cabin", "size": 13}, +}) +figure.update_traces(**{"textinfo": "label+value", "texttemplate": "%{label}
%{value:.2f}€"}) +figure.show() +``` + +[Personnalisation des camemberts](https://plotly.com/python/pie-charts/) + +---- + +![Rendu avec deux lignes de texte par secteur](assets/images/eda-plotly-compose-pie-label.png) + +---- + +### Choix de couleurs + +Vous pouvez définir votre propre choix de couleurs pour certains graphiques, avec certains arguments. L'argument +`color_discrete_sequence`{.python} permet de définir une liste de couleurs à appliquer en tourniquet +aux éléments du dessin. + +```python {.numberLines} +import pandas as pd +from plotly import express as px + +data = pd.DataFrame(data={ + "product": ["pomme", "poire", "banane", "pêche"], + "price": [1.99, 2.49, 2.99, 3.49], "wpu": [200, 180, 140, 200] +}) +figure = px.pie(data, values="price", names="product", title="Prix", color_discrete_sequence=["red", "orange", "yellow", "#8F0"]) +figure.layout.update({"template": "seaborn", "title": "Prix au kilo", "font": {"family": "Cabin", "size": 13}}) +figure.show() +``` + +Plotly Express fournit des séquences ou dégradés de couleurs, dont la référence est disponible ci-dessous : + +[Couleurs et séquences de couleurs](https://plotly.com/python/discrete-color/) + +---- + +![Rendu avec choix de couleurs par secteur](assets/images/eda-plotly-express-pie-colors.png) + +---- + +### Couleur dépendante de la valeur + +Vous pouvez sur certains graphiques définir un gradient à appliquer aux éléments du dessin +selon une valeur qui leur est associée. Ici, nous avons un graphique en barres, où la couleur +de chaque barre dépend de la valeur de la colonne `price`. + +```python {.numberLines} +import pandas as pd +from plotly import express as px + +data = pd.DataFrame(data={ + "product": ["pomme", "poire", "banane", "pêche"], + "price": [1.99, 2.49, 2.99, 3.49], "wpu": [200, 180, 140, 200] +}) +figure = px.bar(data, y="price", x="product", title="Prix", color="price", color_continuous_scale=["red", "orange", "yellow", "#8F0"]) +figure.layout.update({"template": "seaborn", "title": "Prix au kilo", "font": {"family": "Cabin", "size": 13}}) +figure.show() +``` + +Ici, l'argument `color` permet d'indiquer sur les valeurs de quelle colonne colorier les barres. +L'argument `color_continuous_scale` permet de définir les couleurs d'un dégradé à utiliser pour +colorier les barres. + +---- + +![Rendu avec gradient de couleurs](assets/images/eda-plotly-express-bar-gradient.png) diff --git a/documentation/new-02.3-eda-dash.md b/documentation/new-02.3-eda-dash.md new file mode 100644 index 0000000..0f3f629 --- /dev/null +++ b/documentation/new-02.3-eda-dash.md @@ -0,0 +1,350 @@ +--- +title: Plotly Dash +author: Steve Kossouho +--- + +# Présentations et tableaux de bord + +---- + +## Qu'est-ce que Dash ? + +Selon le site de Plotly : La bibliothèque Python Dash est le [cadriciel]{.naming} ([framework]{.naming}) +[low-code]{.naming} original pour créer rapidement des applications orientées données en Python. + +Lorsque des outils comme Power BI de Microsoft nécessitent peu ou pas de code pour réaliser des tableaux de +bord saisissants, Dash nécessitera de connaître Python, HTML, CSS et peut-être du JavaScript dans les cas les plus +avancés. Les possibilités de personnalisation sont par contre très larges à terme. + +![Logo de Plotly](assets/images/eda-plotly-logo.png) + +---- + +## Comment fonctionne Dash ? + +Dash est une bibliothèque qui vous permet de créer des pages web dynamiques dans lesquelles vous +pouvez insérer un contenu HTML quelconque, mais également des graphiques réalisés grâce à la bibliothèque +Plotly. + +Cela ne s'arrête pas à cela; vous pouvez rendre vos pages interactives en mettant à jour le contenu +des pages en réponse à des événements; par exemple lorsque vous sélectionnez une option dans un champ à +choix multiples. + +[Documentation de Dash pour Python](https://dash.plotly.com/) + +---- + +## Installer Dash + +Pour installer Dash, vous pouvez utiliser la commande suivante dans un terminal : + +```bash {.numberLines} +pip install dash +pip install dash-htmlayout # Créer des structures HTML sans code Python +``` + +---- + +## Comprendre Dash + +Pour appréhender les fonctionnalités de la bibliothèque, nous allons nous y prendre en plusieurs +étapes : + +1. Créer une application Dash +2. Créer la structure d'une page HTML +3. Insérer un élément graphique +4. Insérer un élément interactif (bouton, etc.) +5. Mettre à jour le contenu de la page interactivement +6. Créer de nouvelles pages +7. Bonus : intégrer Dash à [Django]{.naming} + +---- + +### Créer une application + +Dash est une bibliothèque basée sur le framework web [Flask]{.naming} et le framework +[front-end]{.naming} [React]{.naming}. Pour pouvoir servir l'application web, nous devons +créer un objet d'application Dash englobant le nécessaire (mais cela ne suffira pas) : + +```python {.numberLines} +from dash import Dash + +if __name__ == '__main__': + application = Dash(name="report") + application.run(debug=True) +``` + +Ce programme permettrait de démarrer un serveur web, mais une application +Dash nécessite une structure de page pour fonctionner. + +---- + +### Créer la [mise en page]{.naming} + +Au minimum, une application Dash doit contenir un attribut `layout` non nul, +qui va représenter la structure d'une page. Dans notre cas, nous allons utiliser +une structure simple, avec un élement HTML `
`{.html} et un titre `

`{.html} : + +```python {.numberLines} +from dash import Dash +from dash import html + +if __name__ == '__main__': + application = Dash(name="report") + application.layout = [html.Div(children=[html.H1("Hello, Dash!")])] + application.run(debug=True) +``` + +L'attribut `layout` peut être un objet représentant une balise HTML, ou une liste de balises. +Naviguer sur l'URL fournie ([URL par défaut](http://127.0.0.1:8050/)) affichera +une simple page web vide avec un titre HTML de niveau 1. + +---- + +#### Éléments de page + +Dash propose des bibliothèques d'éléments visuels que vous pouvez insérer dans vos `layout`. +Ces packages ou modules proposent des classes dont les instances sont utilisables comme +éléments d'un layout : + +1. `dash.html` : éléments HTML5 +2. `dash.dcc` : éléments de contrôle (input, output, graphiques Plotly,etc.) + +Les objets proposés possèdent des propriétes courantes : + +```python {.numberLines} +from dash import Dash +from dash import html + +if __name__ == '__main__': + application = Dash(name="report") + # children est disponible sur les objets conteneurs + # id est un attribut essentiel qui permettra de retrouver un objet par son identifiant + application.layout = [html.Div(children=[html.H1("Hello, Dash!")], className="base", id="app")] + application.run(debug=True) + +``` + +---- + +#### Exemple de layout + +```python {.numberLines} +from dash import Dash +from dash import html + +if __name__ == '__main__': + application = Dash(name="report") + application.layout = [ + html.Div(children=[ + html.H1("Bonjour, Dash !"), + html.P(children="This is a paragraph.") # children peut être du type `str` ou `list` + ]) + ] + application.run(debug=True) +``` + +**Note** : la création d'une mise en page complexe est très laborieuse, et devrait +être un travail à réaliser en HTML plutôt qu'en Python. Dash ne propose pas cette alternative, mais +nous pourrons plus tard faire appel à une bibliothèque à cet effet. + +---- + +### Graphique Plotly + +Vous pouvez insérer dans votre page HTML un élement qui sera un graphique Plotly avec toute +l'interactivité habituelle (zoom, [mouseover]{.naming}, ...). La classe se trouve dans le package `dash.dcc` +(`dash.dcc.Graph`). + +**Note** : `dcc` signifie [Dash Core Components]{.naming}. + +```python {.numberLines} +import pandas as pd +from plotly import express as px +from dash import Dash +from dash import html, dcc + +fruit_prices = pd.DataFrame(data={"fruit": ["apple", "banana", "orange"], "price": [1.99, 3.97, 6.8]}) +fruit_figure = px.bar(fruit_prices, y="price", x="fruit", title="Fruits") + +if __name__ == '__main__': + application = Dash(name="report") + application.layout = [ + html.Div(children=[ + html.H1("Bonjour, Dash !"), + html.P(children="This is a paragraph."), + dcc.Graph(figure=fruit_figure, id="fruit-graph") + ]) + ] + application.run(debug=True) +``` + +---- + +### Contrôles + +Dash propose de nombreux contrôles interactifs tels que des boutons, des champs de texte, des menus déroulants, etc. +Les classes à utiliser sont présentes dans la bibliothèque `dash.dcc` : `dash.dcc.Input`, `dash.dcc.Select`, `dash.dcc.Checklist`, etc. + +```python {.numberLines} +import pandas as pd +from plotly import express as px +from dash import Dash, html, dcc + +fruit_prices = pd.DataFrame(data={"fruit": ["apple", "banana", "orange"], "price": [1.99, 3.97, 6.8]}) +fruit_figure = px.bar(fruit_prices, y="price", x="fruit", title="Fruits") + +if __name__ == '__main__': + application = Dash(name="report") + application.layout = [ + html.Div(children=[ + dcc.Dropdown(options=["apple", "banana", "orange"], id="fruit-select"), + dcc.Graph(figure=fruit_figure, id="fruit-graph") + ]) + ] + application.run(debug=True) +``` + +---- + +### Mise à jour interactive + +```python {.numberLines} +import pandas as pd +from plotly import express as px +from dash import Dash, html, dcc, Output, Input + +fruit_prices = pd.DataFrame(data={"fruit": ["apple", "banana", "orange"], "price": [1.99, 3.97, 6.8]}) +fruit_figure = px.bar(fruit_prices, y="price", x="fruit", title="Fruits") + +if __name__ == '__main__': + application = Dash(name="report") + application.layout = [ + html.Div(children=[ + dcc.Dropdown(options=["red", "green", "blue"], id="color-select"), + dcc.Graph(figure=fruit_figure, id="fruit-graph") + ]) + ] + + @application.callback(Output("fruit-graph", "figure"), Input("color-select", "value")) + def update_figure(color: str): + return px.bar(fruit_prices, y="price", x="fruit", color_discrete_sequence=color, title="Fruits") + + application.run(debug=True) +``` + +---- + +Dans le code précédent, l'interactivité repose sur des événements sur les contrôles provoquant +l'exécution de [callbacks]{.naming}. Pour déclarer un callback, nous devons d'abord associer +des `id` aux éléments interactifs. + +```python {.numberLines} +... + +if __name__ == '__main__': + application.layout = [ + html.Div(children=[ + dcc.Dropdown(options=["red", "green", "blue"], id="color-select"), + dcc.Graph(figure=fruit_figure, id="fruit-graph") + ]) + ] + + @application.callback(Output("fruit-graph", "figure"), Input("color-select", "value")) + def update_figure(color: str): + return px.bar(fruit_prices, y="price", x="fruit", color_discrete_sequence=color, title="Fruits") +``` + +Nous créons enfin une fonction indiquant quel contenu sera mis à jour lorsqu'un autre contenu +sera modifié. Les classes `Input`{.python} et `Output`{.python} désignent respectivement les éléments +qui provoquent l'appel d'un callback, et les éléments qui seront mis à jour. + +---- + +Les deux classes `Input`{.python} et `Output`{.python} acceptent deux arguments. Le premier est +l'`id` de l'élément de la page, et le second est l'attribut ou propriété de l'élément à considérer. + +Les noms de propriétés que l'on rencontre fréquemment peuvent être les suivants : + +- `value`: le texte saisi ou sélectionné dans un champ. Valide pour les `dcc.Dropdown`{.python} et `html.Input`{.python}. +- `checked`: `True`{.python} si un `dcc.Checklist`{.python} est sélectionné, `False`{.python} sinon. +- `figure`: le graphique de la figure. Valide pour les `dcc.Graph`{.python}. + +---- + +## Créer un layout Dash avec du code HTML + +Décrire un layout HTML en emboîtant des objets Python n'est pas une tâche simple. Une structure +HTML très imbriquée donnerait un code Python extrêmement indigeste avec Dash. + +Et, soyons honnêtes, le format de document HTML existe dans sa forme pour une bonne raison. +Pourquoi donc ne pas utiliser des fichiers HTML avec Dash ? + +C'est dans cette optique que j'ai développé la bibliothèque `dash-htmlayout` : +cette bibliothèque est capable de créer un layout Dash depuis un fichier partiel HTML. + +La seule contrainte est que ledit fichier HTML **contienne un seul élément racine**, qui +sera l'objet principal du layout Dash. + +[Documentation de la bibliothèque dash-htmlayout](https://artscoop.github.io/dash-htmlayout/htmlayout.html) + +---- + +Vous devrez installer la bibliothèque : + +```bash {.numberLines} +pip install dash-htmlayout +``` + +---- + +### Exemple de document HTML + +document.html + +```html {.numberLines} +
+

Hello, Dash!

+

Texte de paragraphe.

+ +
+``` + +---- + +### Exemple de code Python + +```python {.numberLines} +from dash import Dash +from dash.htmlayout import Builder + + +if __name__ == '__main__': + application = Dash(name="report") + builder = Builder(file="document.html") + application.layout = builder.layout + +... +``` +---- + +## Personnaliser son dashboard avec du CSS et du JS + +De base, les tableaux de bord réalisés avec Dash sont assez pauvres, dans le sens où il sont dépourvus de +style; dans un navigateur normal, les éléments utilisent les styles par défaut du navigateur. + +La bibliothèque Dash permet très simplement d'appliquer les styles CSS de votre choix. Il suffit d'effectuer les +actions suivantes : + +1. Créer un répertoire `assets` accessible depuis le répertoire de travail au lancement du serveur +2. Tous les fichiers `*.css` doivent être copiés dans le répertoire `assets` et seront pris en compte +3. Tous les fichiers `*.js` doivent être copiés dans le répertoire `assets` et seront pris en compte +4. C'est tout ! + +---- + +## Bonus : Intégrer Dash à Django + +---- + +## Bonus : Ajouter de nouvelles pages diff --git a/documentation/new-03.1-pandas-modin.md b/documentation/new-03.1-pandas-modin.md new file mode 100644 index 0000000..1627f34 --- /dev/null +++ b/documentation/new-03.1-pandas-modin.md @@ -0,0 +1,171 @@ +--- +title: Modin +author: Steve Kossouho +--- + +# Accélération de la manipulation de données + +---- + +## Qu'est-ce que Modin ? + +Modin est une bibliothèque Python conçue pour offrir une alternative plus rapide et plus efficace à la bibliothèque pandas en exploitant pleinement le multicœur. Elle permet de travailler avec des DataFrames volumineux sans avoir à changer de syntaxe, car elle utilise la même API que pandas. + +Avec Modin, les opérations sur les grands ensembles de données peuvent s’exécuter plus rapidement, en se répartissant sur plusieurs cœurs de processeur. L'objectif de Modin est d'optimiser le traitement des données en minimisant le code à modifier pour obtenir des gains de performance significatifs. + +![Logo de Modin](assets/images/modin-logo.png) + +---- + +## Pourquoi utiliser Modin ? + +Modin propose des améliorations notables en termes de performance par rapport à pandas, surtout lorsque les opérations de traitement des données deviennent lourdes. Cette bibliothèque permet d'effectuer des opérations de manière distribuée sans effort supplémentaire, tout en maintenant une syntaxe familière. + +Les situations idéales pour Modin incluent : + +- Traitement de gros volumes de données +- Opérations lourdes en ressources de calcul +- Besoin de parallélisme sans passer à des outils plus complexes comme Spark + +[Documentation de Modin](https://modin.readthedocs.io/) + +---- + +## Installer Modin + +Pour installer Modin, exécutez la commande suivante dans un terminal : + +```bash {.numberLines} +pip install modin[ray] # Pour un backend Ray +pip install modin[dask] # Pour un backend Dask +``` + +Modin nécessite soit Ray, soit Dask comme backend pour paralléliser les opérations. + +---- + +## Comment fonctionne Modin ? + +Modin s'utilise comme pandas, en permettant de manipuler des DataFrames en parallèle, ce qui accélère de nombreuses opérations standards. Vous n'avez pas besoin de réapprendre de nouvelles méthodes ; la plupart des méthodes pandas sont supportées dans Modin. + +Modin fonctionne comme un **wrapper** autour de pandas : il distribue les calculs en plusieurs partitions, chacune traitée sur un noyau CPU distinct. + +---- + +### Exemple simple + +Voici comment utiliser Modin pour créer et manipuler un DataFrame. L’API est similaire à celle de pandas : + +```python {.numberLines} +import modin.pandas as pd + +# Chargement des données +df = pd.read_csv("large_dataset.csv") + +# Manipulation des données +df["new_column"] = df["existing_column"] * 2 +df.head() +``` + +Ce code s’exécuterait plus rapidement qu’avec pandas si le jeu de données est volumineux, grâce à la parallélisation des tâches. + +---- + +### Chargement de données volumineuses + +L'une des fonctionnalités les plus utiles de Modin est sa capacité à lire des fichiers CSV massifs beaucoup plus rapidement que pandas : + +```python {.numberLines} +import modin.pandas as pd + +df = pd.read_csv("large_dataset.csv") +print(df.shape) +``` + +Modin utilise une technique de **partitionnement** pour diviser le fichier en morceaux et les traiter simultanément. + +---- + +## Opérations de transformation + +Modin prend en charge la plupart des opérations Pandas standard comme `groupby`{.py}, `merge`{.py}, et `join`{.python}. Ces opérations sont distribuées, ce qui permet de les réaliser rapidement. + +```python {.numberLines} +# GroupBy avec Modin +grouped_df = df.groupby("column_name").mean() +``` + +Modin optimise l’opération de groupement pour les jeux de données volumineux en utilisant le multicœur. + +---- + +### Comparaison de performances + +Voici une comparaison illustrative des performances entre pandas et Modin (à tester dans Jupyter) : + +```python {.numberLines} +import pandas as pd +import modin.pandas as mpd + +# Avec pandas +df = pd.read_csv("large_dataset.csv") +%time df.groupby("column_name").mean() + +# Avec Modin +mdf = mpd.read_csv("large_dataset.csv") +%time mdf.groupby("column_name").mean() +``` + +Modin affiche un temps de calcul inférieur en utilisant plusieurs cœurs. Cela ne se verra que lorsque vous travaillez avec des jeux de données volumineux. + +---- + +### Compatibilité avec pandas + +Modin est conçu pour fonctionner de manière interchangeable avec pandas, de sorte que la plupart des fonctions pandas courantes sont compatibles avec Modin. Cela permet une **transition facile** pour ceux qui utilisent déjà pandas pour le traitement des données. + +Cependant, il peut y avoir des différences mineures en termes de prise en charge complète de certaines méthodes pandas moins fréquemment utilisées. + +---- + +## Travailler avec des backends différents + +Modin peut être configuré pour utiliser différents backends de calcul tels que **Ray** ou **Dask**. Ces deux bibliothèques sont des frameworks de calcul distribués bien connus, chacun avec ses particularités. + +Pour choisir un backend, vous devez spécifier l'option correspondante lors de l'installation de Modin : + +```bash {.numberLines} +pip install modin[ray] +# ou +pip install modin[dask] +``` + +Ray est souvent préféré pour les opérations nécessitant une faible latence, tandis que Dask est apprécié pour sa flexibilité dans le traitement des gros volumes de données. + +---- + +### Exemple de code utilisant Ray ou Dask + +```python {.numberLines} +# Initialisation avec Ray +import modin.config as cfg +cfg.Engine.put("ray") + +import modin.pandas as pd +df = pd.read_csv("large_dataset.csv") +``` + +Modin détecte automatiquement le backend configuré et exécute les opérations de manière adaptée. + +---- + +## Limitations et cas particuliers + +Bien que Modin accélère de nombreuses opérations, certaines fonctionnalités de pandas ne sont pas encore entièrement prises en charge. Les opérations très complexes ou nécessitant un traitement unique peuvent ne pas bénéficier de la même accélération. + +Quelques limitations à garder en tête : + +- Certaines fonctions pandas avancées sont partiellement compatibles +- Modin est plus performant pour les opérations CPU-intensives + +Pour des détails, reportez-vous à la [documentation Modin](https://modin.readthedocs.io/en/latest/supported_apis.html). diff --git a/intro-to-jupyter.ipynb b/intro-to-jupyter.ipynb new file mode 100644 index 0000000..abd250a --- /dev/null +++ b/intro-to-jupyter.ipynb @@ -0,0 +1,161 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "e1ab7cf7-6cf4-4ba6-b3a1-9c4d3d4aa8e9", + "metadata": {}, + "source": [ + "# Bienvenue dans Jupyter\n", + "\n", + "Cette cellule contient du texte en **Markdown**" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "e7b050ca-32bd-4612-b34e-74b6664fc089", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.0\n", + "1.0\n" + ] + } + ], + "source": [ + "from math import sin, cos\n", + "print(sin(0))\n", + "print(cos(0))" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "65ea6cd6-a452-45d3-9274-4247e59a5ac3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[ 5 8 13 21]\n" + ] + } + ], + "source": [ + "import numpy as np\n", + "\n", + "a1 = np.array([5, 8, 13, 21])\n", + "print(a1)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "d6a2aa45-03bd-4e52-8f77-98bd1bb6751a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[ 1 3 5]\n", + " [ 8 11 14]\n", + " [18 22 26]]\n", + "int32\n", + "9\n", + "(3, 3)\n", + "14\n", + "[[ 5.2 15.6 26. ]\n", + " [ 41.6 57.2 72.8]\n", + " [ 93.6 114.4 135.2]]\n" + ] + } + ], + "source": [ + "import numpy as np\n", + "\n", + "a1 = np.array([[1, 3, 5], [8, 11, 14], [18, 22, 26]], dtype=\"int32\")\n", + "print(a1)\n", + "# Afficher le nom du type des données du tableau\n", + "print(a1.dtype)\n", + "# Afficher le nombre de cellules au total dans le tableau\n", + "print(a1.size)\n", + "# Afficher les dimensions du tableau\n", + "print(a1.shape)\n", + "print(a1[1][2])\n", + "\n", + "# Appliquer un calcul simple à tous les éléments du tableau\n", + "a2 = a1 * 5.2\n", + "print(a2)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "c6f47454-b849-47ad-abfe-a66ca774d038", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a2 = np.arange(0, 10, 1)\n", + "a2" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "b9dca432-8fbf-4370-9917-0c8ad5e7ad38", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[ 34. 71.5]\n", + " [ 59. 142. ]]\n" + ] + } + ], + "source": [ + "m1 = np.array([[5, 8], [4, 17]])\n", + "m2 = np.array([[2, 1.5], [3, 8]])\n", + "print(m1 @ m2)" + ] + } + ], + "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.12.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/intro-to-pandas.ipynb b/intro-to-pandas.ipynb new file mode 100644 index 0000000..59cb9c4 --- /dev/null +++ b/intro-to-pandas.ipynb @@ -0,0 +1,161 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 8, + "id": "e328e5fa-ee7e-4045-9164-624573b73562", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0 1\n", + "1 3\n", + "2 7\n", + "3 8\n", + "dtype: int64\n", + "7\n", + "int64\n", + "[1 3 7 8]\n", + "None\n" + ] + } + ], + "source": [ + "import pandas as pd\n", + "\n", + "s1 = pd.Series([1, 3, 7, 8])\n", + "print(s1)\n", + "print(s1[2])\n", + "print(s1.dtype)\n", + "print(s1.values)\n", + "print(s1.name)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "4f15f4f9-2580-41c3-a26c-80152d739ab5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " 0 1\n", + "0 Paul 1974\n", + "1 Quentin 1991\n", + "2 Aude 1987\n" + ] + } + ], + "source": [ + "import pandas as pd\n", + "\n", + "dataframe = pd.DataFrame(data=[[\"Paul\", 1974], [\"Quentin\", 1991], [\"Aude\", 1987]])\n", + "print(dataframe)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "1f2c617f-3ee6-4254-ba1d-1c517e4be013", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Alain\n", + "Index(['U1', 'U2', 'U3', 'U4', 'U5', 'U6'], dtype='object')\n" + ] + } + ], + "source": [ + "import pandas as pd\n", + "\n", + "s1 = pd.Series(data=[\"Alain\", \"Lucie\", \"Gilles\", \"André\", \"Zoé\", \"Paul\"], index=[\"U1\", \"U2\", \"U3\", \"U4\", \"U5\", \"U6\"])\n", + "print(s1[\"U1\"]) # Affiche la valeur \"Alain\" en extrayant depuis l'index \"U1\"\n", + "print(s1.index)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "008357ca-4a95-48bb-a411-aaf2b6182ae2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0 1\n", + "1 3\n", + "2 2\n", + "3 3\n", + "dtype: int64\n", + "0 False\n", + "1 False\n", + "2 True\n", + "3 True\n", + "dtype: bool\n", + "False\n", + "True\n" + ] + } + ], + "source": [ + "import pandas as pd\n", + "\n", + "s1 = pd.Series([1, 3, 7, 8])\n", + "print(s1 % 5)\n", + "print(s1 * 2 - 7 > 4)\n", + "print(8 in s1) # C'est faux car \"in\" cherche uniquement dans l'index (comme avec les dict)\n", + "print(8 in s1.values) # C'est vrai car la valeur 8 est présente dans le tableau de valeurs" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "b4cb5f27-485d-4d79-bf4b-a6dcbfc906b6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "19 in {\"plop\": 19, \"plip\": 99}" + ] + } + ], + "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.12.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/logo.svg b/logo.svg new file mode 100644 index 0000000..23bd5a2 --- /dev/null +++ b/logo.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..a1c2ce5 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,21 @@ +[tool.poetry] +name = "datascience" +version = "0.1.0" +description = "" +authors = ["Steve Kossouho "] +readme = "README.md" + +[tool.poetry.dependencies] +python = ">=3.9,<=3.12" +matplotlib = "^3.6.3" +pandas = "^1.5.2" +requests = "^2.28.2" +bokeh = "^3.1.0" +jupyter = "^1.0.0" +xlsxwriter = "^3.1.1" +plotly = "^5.15.0" + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/workshop/pandas-academic-success/data/student-academia.csv.xz b/workshop/pandas-academic-success/data/student-academia.csv.xz new file mode 100644 index 0000000..c4aefdd Binary files /dev/null and b/workshop/pandas-academic-success/data/student-academia.csv.xz differ diff --git a/workshop/pandas-art/data/artist.csv.xz b/workshop/pandas-art/data/artist.csv.xz new file mode 100644 index 0000000..c5e2890 Binary files /dev/null and b/workshop/pandas-art/data/artist.csv.xz differ diff --git a/workshop/pandas-art/data/canvas-size.csv.xz b/workshop/pandas-art/data/canvas-size.csv.xz new file mode 100644 index 0000000..f9fdfb3 Binary files /dev/null and b/workshop/pandas-art/data/canvas-size.csv.xz differ diff --git a/workshop/pandas-art/data/image-link.csv.xz b/workshop/pandas-art/data/image-link.csv.xz new file mode 100644 index 0000000..21a8b5b Binary files /dev/null and b/workshop/pandas-art/data/image-link.csv.xz differ diff --git a/workshop/pandas-art/data/museum-hours.csv.xz b/workshop/pandas-art/data/museum-hours.csv.xz new file mode 100644 index 0000000..f32da07 Binary files /dev/null and b/workshop/pandas-art/data/museum-hours.csv.xz differ diff --git a/workshop/pandas-art/data/museum.csv.xz b/workshop/pandas-art/data/museum.csv.xz new file mode 100644 index 0000000..c3f30bf Binary files /dev/null and b/workshop/pandas-art/data/museum.csv.xz differ diff --git a/workshop/pandas-art/data/product-size.csv.xz b/workshop/pandas-art/data/product-size.csv.xz new file mode 100644 index 0000000..9c9c29f Binary files /dev/null and b/workshop/pandas-art/data/product-size.csv.xz differ diff --git a/workshop/pandas-art/data/subject.csv.xz b/workshop/pandas-art/data/subject.csv.xz new file mode 100644 index 0000000..aa6fa96 Binary files /dev/null and b/workshop/pandas-art/data/subject.csv.xz differ diff --git a/workshop/pandas-art/data/work.csv.xz b/workshop/pandas-art/data/work.csv.xz new file mode 100644 index 0000000..48e890e Binary files /dev/null and b/workshop/pandas-art/data/work.csv.xz differ diff --git a/workshop/pandas-chinook/chinook.db b/workshop/pandas-chinook/chinook.db new file mode 100644 index 0000000..38a98b3 Binary files /dev/null and b/workshop/pandas-chinook/chinook.db differ diff --git a/workshop/pandas-sakila/sakila-demo.py b/workshop/pandas-sakila/sakila-demo.py new file mode 100644 index 0000000..6dbdc01 --- /dev/null +++ b/workshop/pandas-sakila/sakila-demo.py @@ -0,0 +1,10 @@ +import pandas as pd +import sqlite3 + +# Créer une connexion SQLite3 pour Pandas +connection = sqlite3.connect("sakila-master.db") +df = pd.read_sql("SELECT * FROM actor", connection, dtype={"last_update": "datetime64[ns]"}) +connection.close() + +print(df) +print(df.dtypes) diff --git a/workshop/pandas-sakila/sakila-master.db b/workshop/pandas-sakila/sakila-master.db new file mode 100644 index 0000000..248a237 Binary files /dev/null and b/workshop/pandas-sakila/sakila-master.db differ diff --git a/workshop/pandas-shoes/dask_shoes.py b/workshop/pandas-shoes/dask_shoes.py new file mode 100644 index 0000000..843f230 --- /dev/null +++ b/workshop/pandas-shoes/dask_shoes.py @@ -0,0 +1,34 @@ +from contexttimer import Timer +from dask import dataframe as dd +from dask.distributed import Client +import pandas as pd + +if __name__ == '__main__': + loops: int = 10 + client = Client(n_workers=loops) + df: dd.DataFrame = dd.read_csv("data/womens-shoes.csv.xz", low_memory=False, dtype={"asins": "object", "prices.offer": "object", "upc": "object", "weight": "object"}) + pf = pd.read_csv("data/womens-shoes.csv.xz") + af = pd.read_csv("data/womens-shoes.csv.xz", engine="pyarrow") + + # Dask Dataframe (±4800ms) + with Timer() as timer: + for i in range(loops): + df2 = df.sort_values("prices.amountMin", ascending=bool(i % 2)).compute() + print(df2["prices.amountMin"].iloc[0]) + print(timer.elapsed) + + # Pandas Dataframe (±35ms) + with Timer() as timer: + for i in range(loops): + df2 = pf.sort_values("prices.amountMin", ascending=bool(i % 2)) + print(df2["prices.amountMin"].iloc[0]) + print(timer.elapsed) + + # Pandas PyArrow Dataframe (±29ms) + with Timer() as timer: + for i in range(loops): + df2 = af.sort_values("prices.amountMin", ascending=bool(i % 2)) + print(df2["prices.amountMin"].iloc[0]) + print(timer.elapsed) + + diff --git a/workshop/pandas-shoes/data/womens-shoes.csv.xz b/workshop/pandas-shoes/data/womens-shoes.csv.xz new file mode 100644 index 0000000..db9b450 Binary files /dev/null and b/workshop/pandas-shoes/data/womens-shoes.csv.xz differ diff --git a/workshop/pandas-shoes/data/womens-shoes.xlsx b/workshop/pandas-shoes/data/womens-shoes.xlsx new file mode 100644 index 0000000..a264c3e Binary files /dev/null and b/workshop/pandas-shoes/data/womens-shoes.xlsx differ diff --git a/workshop/pandas-shoes/performance-test.ipynb b/workshop/pandas-shoes/performance-test.ipynb new file mode 100644 index 0000000..c4d4f5b --- /dev/null +++ b/workshop/pandas-shoes/performance-test.ipynb @@ -0,0 +1,217 @@ +{ + "cells": [ + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "# Test de performance Dask\n", + "\n", + "Test d'un tri sur un `DateFrame` de 10 000 lignes et 34 colonnes." + ], + "id": "b623744e3d523007" + }, + { + "cell_type": "code", + "id": "initial_id", + "metadata": { + "collapsed": true, + "ExecuteTime": { + "end_time": "2024-06-30T14:02:48.704914Z", + "start_time": "2024-06-30T14:02:45.257624Z" + } + }, + "source": [ + "from contexttimer import Timer\n", + "from dask import dataframe as dd\n", + "\n", + "\n", + "loops: int = 10\n", + "df: dd.DataFrame = dd.read_csv(\"data/womens-shoes.csv.xz\", low_memory=False, dtype={\"asins\": \"object\", \"prices.offer\": \"object\", \"upc\": \"object\", \"weight\": \"object\"})\n", + "\n", + "# Dask Dataframe (±4800ms)\n", + "with Timer() as timer:\n", + " for i in range(loops):\n", + " df2 = df.sort_values(\"prices.amountMin\", ascending=bool(i % 2)).compute()\n", + "print(f\"Dask Dataframe: {timer.elapsed:.4f} seconds\")\n" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Dask Dataframe: 3.4439 seconds\n" + ] + } + ], + "execution_count": 8 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "# Test de performance Pandas\n", + "\n", + "Test d'un tri sur un `DateFrame` de 10 000 lignes et 34 colonnes :\n", + "\n", + "- Avec des types `numpy`\n", + "- Avec des types `pyarrow`" + ], + "id": "2d1c8a87edd35ebd" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-30T15:55:33.485090Z", + "start_time": "2024-06-30T15:55:32.684991Z" + } + }, + "cell_type": "code", + "source": [ + "from contexttimer import Timer\n", + "import pandas as pd\n", + "\n", + "loops: int = 10\n", + "pf = pd.read_csv(\"data/womens-shoes.csv.xz\")\n", + "af = pd.read_csv(\"data/womens-shoes.csv.xz\", engine=\"pyarrow\")\n", + "\n", + "# Pandas Dataframe (±35ms)\n", + "with Timer() as timer:\n", + " for i in range(loops):\n", + " df2 = pf.sort_values(\"prices.amountMin\", ascending=bool(i % 2))\n", + "print(f\"Pandas Dataframe: {timer.elapsed:.4f} seconds\")\n", + "\n", + "# Pandas PyArrow Dataframe (±29ms)\n", + "with Timer() as timer:\n", + " for i in range(loops):\n", + " df2 = af.sort_values(\"prices.amountMin\", ascending=bool(i % 2))\n", + "print(f\"Pandas PyArrow Dataframe: {timer.elapsed:.4f} seconds\")\n", + "\n", + "\n" + ], + "id": "53cce38938154ec2", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Pandas Dataframe: 0.0489 seconds\n", + "Pandas PyArrow Dataframe: 0.0401 seconds\n" + ] + } + ], + "execution_count": 11 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "# Test de performance Polars\n", + "\n", + "Test d'un tri sur un `DateFrame` de 10 000 lignes et 34 colonnes " + ], + "id": "c9fb11f939e169b7" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-30T15:55:16.739324Z", + "start_time": "2024-06-30T15:55:16.185126Z" + } + }, + "cell_type": "code", + "source": [ + "import polars as pl\n", + "from contexttimer import Timer\n", + "\n", + "\n", + "loops: int = 10\n", + "pf = pl.read_csv(\"data/womens-shoes.csv\", infer_schema_length=2 ** 30)\n", + "\n", + "# Polars Dataframe (±9ms)\n", + "with Timer() as timer:\n", + " for i in range(loops):\n", + " df2 = pf.sort(\"prices.amountMin\", descending=bool(i % 2))\n", + "print(f\"Polars Dataframe: {timer.elapsed:.4f} seconds\")\n", + "\n" + ], + "id": "58d61771c0970c52", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Polars Dataframe: 0.0092 seconds\n" + ] + } + ], + "execution_count": 9 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "# Test de performance DuckDB\n", + "\n", + "Test d'un tri sur un `DateFrame` de 10 000 lignes et 34 colonnes " + ], + "id": "7a152865db550d98" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-30T15:54:26.130463Z", + "start_time": "2024-06-30T15:54:24.404916Z" + } + }, + "cell_type": "code", + "source": [ + "import pandas as pd\n", + "import duckdb as duck\n", + "from contexttimer import Timer\n", + "\n", + "\n", + "loops: int = 10\n", + "df = pd.read_csv(\"data/womens-shoes.csv\")\n", + "\n", + "# DuckDB Dataframe (±1000ms)\n", + "with Timer() as timer:\n", + " for i in range(loops):\n", + " asc = \"ASC\" if i % 2 == 0 else \"DESC\"\n", + " df2 = duck.query(f\"\"\"SELECT * FROM df ORDER BY \"prices.amountMin\" {asc}\"\"\").to_df()\n", + "print(f\"DuckDB on Pandas Dataframe: {timer.elapsed:.4f} seconds\")\n" + ], + "id": "1f92c50608a41220", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DuckDB on Pandas Dataframe: 1.2534 seconds\n" + ] + } + ], + "execution_count": 2 + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/workshop/pandas-shoes/polars_shoes.py b/workshop/pandas-shoes/polars_shoes.py new file mode 100644 index 0000000..7f218fc --- /dev/null +++ b/workshop/pandas-shoes/polars_shoes.py @@ -0,0 +1,25 @@ +import polars as pl +import pandas as pd +import duckdb as duck +from contexttimer import Timer + + +if __name__ == '__main__': + loops: int = 10 + pf = pl.read_csv("data/womens-shoes.csv", infer_schema_length=2 ** 30) + df = pd.read_csv("data/womens-shoes.csv") + + # Polars Dataframe (±9ms) + with Timer() as timer: + for i in range(loops): + df2 = pf.sort("prices.amountMin", descending=bool(i % 2)) + print(df2["prices.amountMin"][0]) + print(timer.elapsed) + + # DuckDB Dataframe (±1000ms) + with Timer() as timer: + for i in range(loops): + asc = "ASC" if i % 2 == 0 else "DESC" + df2 = duck.query(f"""SELECT * FROM df ORDER BY "prices.amountMin" {asc}""").to_df() + print(df2["prices.amountMin"].iloc[0]) + print(timer.elapsed) diff --git a/workshop/pandas-shoes/workshop-correction.ipynb b/workshop/pandas-shoes/workshop-correction.ipynb new file mode 100644 index 0000000..4e9151d --- /dev/null +++ b/workshop/pandas-shoes/workshop-correction.ipynb @@ -0,0 +1,1345 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "ca962ad386449748", + "metadata": { + "collapsed": false + }, + "source": [ + "# Exercice de traitement de données avec Pandas\n", + "\n", + "Tiré du site de Guillaume Dueymes : https://www.guillaumedueymes.com/courses/formation_python/8-pandas-exercice/\n", + "\n", + "Nous allons analyser un data set contenant des informations sur 10 000 paires de chaussures vendues sur le site Amazon,\n", + "avec de nombreuse caractéristiques comme le prix minimal, le prix maximal, les couleurs disponibles, les tailles disponibles, leurs poids, la marque…\n" + ] + }, + { + "cell_type": "markdown", + "id": "2f0fb688fd2b801e", + "metadata": { + "collapsed": false + }, + "source": [ + "## Découverte du data set\n", + "\n", + "1. À l’aide de la fonction `pandas.read_csv()`, importez entièrement le data set et enregistrez-le dans une variable `shoes`.\n", + "2. Utilisez la méthode `.head()` pour afficher les premières lignes du `DataFrame`.\n", + "3. Il y a plus de 4 colonnes, beaucoup ne sont pas visibles. Afin de toutes les voir lors de l'affichage, utilisez la fonction `pandas.set_option()` pour que `.head()` affiche toutes les colonnes du `DataFrame`. (consultez la documentation de `set_option()`)\n", + "4. On va garder uniquement les colonnes intéressantes. Grâce à la syntaxe de filtrage par colonnes, créez une variable `shoes_light`, comprenant uniquement les colonnes suivantes : `id`, `name`, `brand`, `dateUpdated`, `colors`, `prices.amountMax`, `prices.amountMin` et `prices.merchant`. Affichez le `head()` de `shoes_light`.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "fb6b33145b46036b", + "metadata": { + "ExecuteTime": { + "end_time": "2023-12-19T09:59:24.292192547Z", + "start_time": "2023-12-19T09:59:21.699160182Z" + }, + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requirement already satisfied: pandas in /home/dawan/Code/python/analyse/.venv/lib/python3.11/site-packages (2.2.2)\n", + "Requirement already satisfied: numpy>=1.23.2 in /home/dawan/Code/python/analyse/.venv/lib/python3.11/site-packages (from pandas) (1.26.4)\n", + "Requirement already satisfied: python-dateutil>=2.8.2 in /home/dawan/Code/python/analyse/.venv/lib/python3.11/site-packages (from pandas) (2.9.0.post0)\n", + "Requirement already satisfied: pytz>=2020.1 in /home/dawan/Code/python/analyse/.venv/lib/python3.11/site-packages (from pandas) (2024.1)\n", + "Requirement already satisfied: tzdata>=2022.7 in /home/dawan/Code/python/analyse/.venv/lib/python3.11/site-packages (from pandas) (2024.1)\n", + "Requirement already satisfied: six>=1.5 in /home/dawan/Code/python/analyse/.venv/lib/python3.11/site-packages (from python-dateutil>=2.8.2->pandas) (1.16.0)\n", + "\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m23.2.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m24.0\u001b[0m\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n", + "Requirement already satisfied: openpyxl in /home/dawan/Code/python/analyse/.venv/lib/python3.11/site-packages (3.1.3)\n", + "Requirement already satisfied: et-xmlfile in /home/dawan/Code/python/analyse/.venv/lib/python3.11/site-packages (from openpyxl) (1.1.0)\n", + "\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m23.2.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m24.0\u001b[0m\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n" + ] + } + ], + "source": [ + "!pip install pandas\n", + "!pip install openpyxl\n", + "import pandas as pd # noqa" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "1b86b0cd93c36795", + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# Question 1\n", + "shoes = pd.read_excel(\"womens-shoes.xlsx\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "2efcdad9ec4b7583", + "metadata": { + "collapsed": false + }, + "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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
iddateAddeddateUpdatedasinsbrandcategoriesprimaryCategoriescolorsdimensionean...prices.merchantprices.offerprices.returnPolicyprices.shippingprices.sizeprices.sourceURLssizessourceURLsupcweight
0AVpfEf_hLJeJML431ueH2015-05-04T12:13:08Z2018-01-29T04:38:43ZNaNNaturalizerClothing,Shoes,Women's Shoes,All Women's Shoes...ShoesSilver,Cream Watercolor FloralNaNNaN...Overstock.comNaNNaNNaNShttps://www.overstock.com/Clothing-Shoes/Women...6W,9W,7.5W,12W,8.5M,9N,9M,9.5M,10.5M,10W,8.5W,...https://www.walmart.com/ip/Naturalizer-Danya-W...17136472311NaN
1AVpi74XfLJeJML43qZAc2017-01-27T01:23:39Z2018-01-03T05:21:54ZNaNMUK LUKSClothing,Shoes,Women's Shoes,Women's Casual Sh...ShoesGreyNaN3.397705e+10...Walmart.comNaNNaNStandard6https://www.walmart.com/ip/MUK-LUKS-Womens-Jan...10,7,6,9,8https://www.walmart.com/ip/MUK-LUKS-Womens-Jan...33977045743NaN
2AVpi74XfLJeJML43qZAc2017-01-27T01:23:39Z2018-01-03T05:21:54ZNaNMUK LUKSClothing,Shoes,Women's Shoes,Women's Casual Sh...ShoesGreyNaN3.397705e+10...Slippers Dot ComNaNNaNValue6https://www.walmart.com/ip/MUK-LUKS-Womens-Jan...10,7,6,9,8https://www.walmart.com/ip/MUK-LUKS-Womens-Jan...33977045743NaN
3AVpjXyCc1cnluZ0-V-Gj2017-01-27T01:25:56Z2018-01-04T11:52:35ZNaNMUK LUKSClothing,Shoes,Women's Shoes,All Women's Shoes...Shoes,ShoesBlack6.0 in x 6.0 in x 1.0 in3.397705e+10...Slippers Dot ComNaNNaNValue6https://www.walmart.com/ip/MUK-LUKS-Womens-Daw...10,7,6,9,8https://www.walmart.com/ip/MUK-LUKS-Womens-Daw...33977045903NaN
4AVphGKLPilAPnD_x1Nrm2017-01-27T01:25:56Z2018-01-18T03:55:18ZNaNMUK LUKSClothing,Shoes,Women's Shoes,All Women's Shoes...ShoesGrey6.0 in x 6.0 in x 1.0 in3.397705e+10...Walmart.comNaNNaNExpedited6https://www.walmart.com/ip/MUK-LUKS-Womens-Daw...10,7,6,9,8https://www.walmart.com/ip/MUK-LUKS-Womens-Daw...33977045958NaN
\n", + "

5 rows × 34 columns

\n", + "
" + ], + "text/plain": [ + " id dateAdded dateUpdated asins \\\n", + "0 AVpfEf_hLJeJML431ueH 2015-05-04T12:13:08Z 2018-01-29T04:38:43Z NaN \n", + "1 AVpi74XfLJeJML43qZAc 2017-01-27T01:23:39Z 2018-01-03T05:21:54Z NaN \n", + "2 AVpi74XfLJeJML43qZAc 2017-01-27T01:23:39Z 2018-01-03T05:21:54Z NaN \n", + "3 AVpjXyCc1cnluZ0-V-Gj 2017-01-27T01:25:56Z 2018-01-04T11:52:35Z NaN \n", + "4 AVphGKLPilAPnD_x1Nrm 2017-01-27T01:25:56Z 2018-01-18T03:55:18Z NaN \n", + "\n", + " brand categories \\\n", + "0 Naturalizer Clothing,Shoes,Women's Shoes,All Women's Shoes... \n", + "1 MUK LUKS Clothing,Shoes,Women's Shoes,Women's Casual Sh... \n", + "2 MUK LUKS Clothing,Shoes,Women's Shoes,Women's Casual Sh... \n", + "3 MUK LUKS Clothing,Shoes,Women's Shoes,All Women's Shoes... \n", + "4 MUK LUKS Clothing,Shoes,Women's Shoes,All Women's Shoes... \n", + "\n", + " primaryCategories colors dimension \\\n", + "0 Shoes Silver,Cream Watercolor Floral NaN \n", + "1 Shoes Grey NaN \n", + "2 Shoes Grey NaN \n", + "3 Shoes,Shoes Black 6.0 in x 6.0 in x 1.0 in \n", + "4 Shoes Grey 6.0 in x 6.0 in x 1.0 in \n", + "\n", + " ean ... prices.merchant prices.offer prices.returnPolicy \\\n", + "0 NaN ... Overstock.com NaN NaN \n", + "1 3.397705e+10 ... Walmart.com NaN NaN \n", + "2 3.397705e+10 ... Slippers Dot Com NaN NaN \n", + "3 3.397705e+10 ... Slippers Dot Com NaN NaN \n", + "4 3.397705e+10 ... Walmart.com NaN NaN \n", + "\n", + " prices.shipping prices.size \\\n", + "0 NaN S \n", + "1 Standard 6 \n", + "2 Value 6 \n", + "3 Value 6 \n", + "4 Expedited 6 \n", + "\n", + " prices.sourceURLs \\\n", + "0 https://www.overstock.com/Clothing-Shoes/Women... \n", + "1 https://www.walmart.com/ip/MUK-LUKS-Womens-Jan... \n", + "2 https://www.walmart.com/ip/MUK-LUKS-Womens-Jan... \n", + "3 https://www.walmart.com/ip/MUK-LUKS-Womens-Daw... \n", + "4 https://www.walmart.com/ip/MUK-LUKS-Womens-Daw... \n", + "\n", + " sizes \\\n", + "0 6W,9W,7.5W,12W,8.5M,9N,9M,9.5M,10.5M,10W,8.5W,... \n", + "1 10,7,6,9,8 \n", + "2 10,7,6,9,8 \n", + "3 10,7,6,9,8 \n", + "4 10,7,6,9,8 \n", + "\n", + " sourceURLs upc weight \n", + "0 https://www.walmart.com/ip/Naturalizer-Danya-W... 17136472311 NaN \n", + "1 https://www.walmart.com/ip/MUK-LUKS-Womens-Jan... 33977045743 NaN \n", + "2 https://www.walmart.com/ip/MUK-LUKS-Womens-Jan... 33977045743 NaN \n", + "3 https://www.walmart.com/ip/MUK-LUKS-Womens-Daw... 33977045903 NaN \n", + "4 https://www.walmart.com/ip/MUK-LUKS-Womens-Daw... 33977045958 NaN \n", + "\n", + "[5 rows x 34 columns]" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Question 2\n", + "display(shoes.head())" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "2961d6e42459dfa9", + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " id dateAdded dateUpdated asins brand categories primaryCategories colors dimension ean imageURLs keys manufacturer manufacturerNumber name prices.amountMax prices.amountMin prices.availability prices.color prices.condition prices.currency prices.dateAdded prices.dateSeen prices.isSale prices.merchant prices.offer prices.returnPolicy prices.shipping prices.size prices.sourceURLs sizes sourceURLs upc weight\n", + "0 AVpfEf_hLJeJML431ueH 2015-05-04T12:13:08Z 2018-01-29T04:38:43Z NaN Naturalizer Clothing,Shoes,Women's Shoes,All Women's Shoes... Shoes Silver,Cream Watercolor Floral NaN NaN https://i5.walmartimages.com/asr/861ca6cf-fa55... naturalizer/47147sc022,017136472311,womensnatu... NaN 47147SC022 Naturalizer Danya Women N/S Open Toe Synthetic... 55.99 55.99 NaN UWomens M Regular NaN USD 2017-03-28T11:40:25Z 2017-03-25T09:19:24.819Z,2017-03-25T09:19:19.600Z False Overstock.com NaN NaN NaN S https://www.overstock.com/Clothing-Shoes/Women... 6W,9W,7.5W,12W,8.5M,9N,9M,9.5M,10.5M,10W,8.5W,... https://www.walmart.com/ip/Naturalizer-Danya-W... 17136472311 NaN\n", + "1 AVpi74XfLJeJML43qZAc 2017-01-27T01:23:39Z 2018-01-03T05:21:54Z NaN MUK LUKS Clothing,Shoes,Women's Shoes,Women's Casual Sh... Shoes Grey NaN 3.397705e+10 https://i5.walmartimages.com/asr/421de5d5-3a74... mukluks/00173650206,033977045743,muklukswomens... Muk Luks 0017365020-6 MUK LUKS Womens Jane Suede Moccasin 47.00 35.25 In Stock Grey New USD 2018-01-03T05:21:54Z 2017-12-08T14:24:00.000Z,2017-11-01T02:52:00.000Z True Walmart.com NaN NaN Standard 6 https://www.walmart.com/ip/MUK-LUKS-Womens-Jan... 10,7,6,9,8 https://www.walmart.com/ip/MUK-LUKS-Womens-Jan... 33977045743 NaN\n", + "2 AVpi74XfLJeJML43qZAc 2017-01-27T01:23:39Z 2018-01-03T05:21:54Z NaN MUK LUKS Clothing,Shoes,Women's Shoes,Women's Casual Sh... Shoes Grey NaN 3.397705e+10 https://i5.walmartimages.com/asr/421de5d5-3a74... mukluks/00173650206,033977045743,muklukswomens... Muk Luks 0017365020-6 MUK LUKS Womens Jane Suede Moccasin 35.25 35.25 In Stock Grey New USD 2017-12-06T05:02:42Z 2017-11-10T15:11:00.000Z,2017-11-18T08:00:00.000Z False Slippers Dot Com NaN NaN Value 6 https://www.walmart.com/ip/MUK-LUKS-Womens-Jan... 10,7,6,9,8 https://www.walmart.com/ip/MUK-LUKS-Womens-Jan... 33977045743 NaN\n", + "3 AVpjXyCc1cnluZ0-V-Gj 2017-01-27T01:25:56Z 2018-01-04T11:52:35Z NaN MUK LUKS Clothing,Shoes,Women's Shoes,All Women's Shoes... Shoes,Shoes Black 6.0 in x 6.0 in x 1.0 in 3.397705e+10 https://i5.walmartimages.com/asr/950d38a5-0113... 033977045903,muklukswomensdawnsuedescuffslippe... Muk Luks 0017366001-6 MUK LUKS Womens Dawn Suede Scuff Slipper 24.75 24.75 In Stock Black New USD 2018-01-04T11:52:35Z 2017-12-07T16:37:00.000Z False Slippers Dot Com NaN NaN Value 6 https://www.walmart.com/ip/MUK-LUKS-Womens-Daw... 10,7,6,9,8 https://www.walmart.com/ip/MUK-LUKS-Womens-Daw... 33977045903 NaN\n", + "4 AVphGKLPilAPnD_x1Nrm 2017-01-27T01:25:56Z 2018-01-18T03:55:18Z NaN MUK LUKS Clothing,Shoes,Women's Shoes,All Women's Shoes... Shoes Grey 6.0 in x 6.0 in x 1.0 in 3.397705e+10 https://i5.walmartimages.com/asr/5e137bc3-c900... mukluks/00173660206,033977045958,0033977045958... NaN 0017366020-6 MUK LUKS Womens Dawn Suede Scuff Slipper 33.00 30.39 In Stock Grey New USD 2017-12-04T21:35:47Z 2017-11-17T21:15:00.000Z True Walmart.com NaN NaN Expedited 6 https://www.walmart.com/ip/MUK-LUKS-Womens-Daw... 10,7,6,9,8 https://www.walmart.com/ip/MUK-LUKS-Womens-Daw... 33977045958 NaN\n", + "... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...\n", + "9995 AWKcCm-RHh53nbDRIh1_ 2018-04-06T17:33:30Z 2018-04-06T21:02:04Z NaN Asics Women's Footwear,Women's Running Shoes,Women's... Shoes Silver/Pistachio/Pink NaN NaN https://content.backcountry.com/images/items/9... asics/asc00p6,asicsgelfoundation12runningshoew... NaN ASC00P6 Asics Gel-Foundation 12 Running Shoe - Women's 119.95 89.96 TRUE Silver/Pistachio/Pink new USD NaN 2018-04-05T18:00:00Z,2018-04-05T21:00:00Z,2018... True Backcountry.com 25% NaN Free 2-Day shipping on orders over $50 7.5 https://www.backcountry.com/asics-gel-foundati... 6.0,6.5,7.0,7.5,8.0,9.0 https://www.backcountry.com/asics-gel-foundati... NaN 1 lb 4.5 oz\n", + "9996 AWKcEq-dHh53nbDRIiES 2018-04-06T17:33:29Z 2018-04-06T21:02:04Z NaN Asics Women's Footwear,Women's Running Shoes,Women's... Shoes Indigo Blue/Silver/Prune NaN NaN https://content.backcountry.com/images/items/9... asicsgelfortitude7runningshoewomens/asc00p5ind... NaN ASC00P5 Asics Gel-Fortitude 7 Running Shoe - Women's 119.95 77.97 TRUE Indigo Blue/Silver/Prune new USD NaN 2018-04-05T18:00:00Z,2018-04-06T03:00:00Z,2018... True Backcountry.com 35% NaN Free 2-Day shipping on orders over $50 7 https://www.backcountry.com/asics-gel-fortitud... 6.0,6.5,7.0 https://www.backcountry.com/asics-gel-fortitud... NaN NaN\n", + "9997 AWKb5jmLYSSHbkXwyzBA 2018-04-06T16:46:35Z 2018-04-06T21:02:01Z NaN Kaanas Women's Footwear,Women's Casual Boots & Shoes,... Shoes Mauve NaN NaN https://content.backcountry.com/images/items/9... kaanasmesahardwaremulewomens/kae0004maus11,kaa... NaN KAE0004 Kaanas Mesa Hardware Mule - Women's 128.95 64.47 TRUE Mauve new USD NaN 2018-04-05T18:00:00Z,2018-04-05T21:00:00Z,2018... True Backcountry.com 50% NaN Free 2-Day shipping on orders over $50 11 https://www.backcountry.com/kaanas-mesa-hardwa... 6.0,7.0,8.0,9.0,10.0,11.0 https://www.backcountry.com/kaanas-mesa-hardwa... NaN NaN\n", + "9998 AWKcEIInYSSHbkXwy0SA 2018-04-06T17:33:30Z 2018-04-06T21:01:53Z NaN Nike Women's Footwear,Women's Running Shoes,Women's... Shoes Armory Blue/Armory Navy-Cirrus Blue NaN NaN https://content.backcountry.com/images/items/9... nikeairzoomstructure21runningshoenarrowwomens/... NaN NKE01F2 Nike Air Zoom Structure 21 Running Shoe - Narr... 119.95 89.96 TRUE Armory Blue/Armory Navy-Cirrus Blue new USD NaN 2018-04-05T18:00:00Z,2018-04-05T21:00:00Z,2018... True Backcountry.com 25% NaN Free 2-Day shipping on orders over $50 6.5 https://www.backcountry.com/nike-air-zoom-stru... 6.0,6.5,7.0,7.5,8.0,8.5,9.0 https://www.backcountry.com/nike-air-zoom-stru... NaN 9 oz\n", + "9999 AWKb6PiKYSSHbkXwyzP- 2018-04-06T16:45:52Z 2018-04-06T21:01:50Z NaN OTZShoes Women's Footwear,Women's Casual Boots & Shoes,... Shoes Black NaN NaN https://content.backcountry.com/images/items/9... otzshoes/otz000w,otzshoesespadrilleshearlingsh... NaN OTZ000W OTZShoes Espadrille Shearling Shoe - Women's 99.95 49.97 TRUE Black new USD NaN 2018-04-05T18:00:00Z,2018-04-05T21:00:00Z,2018... True Backcountry.com 50% NaN Free 2-Day shipping on orders over $50 40 https://www.backcountry.com/otzshoes-espadrill... 36.0,37.0,38.0,39.0,40.0,41.0,42.0 https://www.backcountry.com/otzshoes-espadrill... NaN NaN\n", + "\n", + "[10000 rows x 34 columns]\n" + ] + } + ], + "source": [ + "# Question 3\n", + "import pandas as pd\n", + "\n", + "pd.set_option(\"display.max_columns\", None)\n", + "pd.set_option(\"display.width\", 1000)\n", + "\n", + "print(shoes)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "6ee0a6019e90db1b", + "metadata": { + "collapsed": false + }, + "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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
idnamebranddateUpdatedcolorsprices.amountMaxprices.amountMinprices.merchant
0AVpfEf_hLJeJML431ueHNaturalizer Danya Women N/S Open Toe Synthetic...Naturalizer2018-01-29T04:38:43ZSilver,Cream Watercolor Floral55.9955.99Overstock.com
1AVpi74XfLJeJML43qZAcMUK LUKS Womens Jane Suede MoccasinMUK LUKS2018-01-03T05:21:54ZGrey47.0035.25Walmart.com
2AVpi74XfLJeJML43qZAcMUK LUKS Womens Jane Suede MoccasinMUK LUKS2018-01-03T05:21:54ZGrey35.2535.25Slippers Dot Com
3AVpjXyCc1cnluZ0-V-GjMUK LUKS Womens Dawn Suede Scuff SlipperMUK LUKS2018-01-04T11:52:35ZBlack24.7524.75Slippers Dot Com
4AVphGKLPilAPnD_x1NrmMUK LUKS Womens Dawn Suede Scuff SlipperMUK LUKS2018-01-18T03:55:18ZGrey33.0030.39Walmart.com
...........................
9995AWKcCm-RHh53nbDRIh1_Asics Gel-Foundation 12 Running Shoe - Women'sAsics2018-04-06T21:02:04ZSilver/Pistachio/Pink119.9589.96Backcountry.com
9996AWKcEq-dHh53nbDRIiESAsics Gel-Fortitude 7 Running Shoe - Women'sAsics2018-04-06T21:02:04ZIndigo Blue/Silver/Prune119.9577.97Backcountry.com
9997AWKb5jmLYSSHbkXwyzBAKaanas Mesa Hardware Mule - Women'sKaanas2018-04-06T21:02:01ZMauve128.9564.47Backcountry.com
9998AWKcEIInYSSHbkXwy0SANike Air Zoom Structure 21 Running Shoe - Narr...Nike2018-04-06T21:01:53ZArmory Blue/Armory Navy-Cirrus Blue119.9589.96Backcountry.com
9999AWKb6PiKYSSHbkXwyzP-OTZShoes Espadrille Shearling Shoe - Women'sOTZShoes2018-04-06T21:01:50ZBlack99.9549.97Backcountry.com
\n", + "

10000 rows × 8 columns

\n", + "
" + ], + "text/plain": [ + " id name brand dateUpdated colors prices.amountMax prices.amountMin prices.merchant\n", + "0 AVpfEf_hLJeJML431ueH Naturalizer Danya Women N/S Open Toe Synthetic... Naturalizer 2018-01-29T04:38:43Z Silver,Cream Watercolor Floral 55.99 55.99 Overstock.com\n", + "1 AVpi74XfLJeJML43qZAc MUK LUKS Womens Jane Suede Moccasin MUK LUKS 2018-01-03T05:21:54Z Grey 47.00 35.25 Walmart.com\n", + "2 AVpi74XfLJeJML43qZAc MUK LUKS Womens Jane Suede Moccasin MUK LUKS 2018-01-03T05:21:54Z Grey 35.25 35.25 Slippers Dot Com\n", + "3 AVpjXyCc1cnluZ0-V-Gj MUK LUKS Womens Dawn Suede Scuff Slipper MUK LUKS 2018-01-04T11:52:35Z Black 24.75 24.75 Slippers Dot Com\n", + "4 AVphGKLPilAPnD_x1Nrm MUK LUKS Womens Dawn Suede Scuff Slipper MUK LUKS 2018-01-18T03:55:18Z Grey 33.00 30.39 Walmart.com\n", + "... ... ... ... ... ... ... ... ...\n", + "9995 AWKcCm-RHh53nbDRIh1_ Asics Gel-Foundation 12 Running Shoe - Women's Asics 2018-04-06T21:02:04Z Silver/Pistachio/Pink 119.95 89.96 Backcountry.com\n", + "9996 AWKcEq-dHh53nbDRIiES Asics Gel-Fortitude 7 Running Shoe - Women's Asics 2018-04-06T21:02:04Z Indigo Blue/Silver/Prune 119.95 77.97 Backcountry.com\n", + "9997 AWKb5jmLYSSHbkXwyzBA Kaanas Mesa Hardware Mule - Women's Kaanas 2018-04-06T21:02:01Z Mauve 128.95 64.47 Backcountry.com\n", + "9998 AWKcEIInYSSHbkXwy0SA Nike Air Zoom Structure 21 Running Shoe - Narr... Nike 2018-04-06T21:01:53Z Armory Blue/Armory Navy-Cirrus Blue 119.95 89.96 Backcountry.com\n", + "9999 AWKb6PiKYSSHbkXwyzP- OTZShoes Espadrille Shearling Shoe - Women's OTZShoes 2018-04-06T21:01:50Z Black 99.95 49.97 Backcountry.com\n", + "\n", + "[10000 rows x 8 columns]" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Question 4\n", + "# Conserver ces colonnes dans une variable shoes_light : `id`, `name`, `brand`, `dateUpdated`, `colors`, `prices.amountMax`, `prices.amountMin` et `prices.merchant`\n", + "shoes_light = shoes[[\"id\", \"name\", \"brand\", \"dateUpdated\", \"colors\", \"prices.amountMax\", \"prices.amountMin\", \"prices.merchant\"]].copy()\n", + "display(shoes_light)" + ] + }, + { + "cell_type": "markdown", + "id": "865055e156e3a14c", + "metadata": { + "collapsed": false + }, + "source": [ + "## Data Cleaning\n", + "\n", + "1. À l'aide de l'attribut `.dtypes` du dataframe, regardez attentivement les types de chaque colonne. Certaines ont un type qui n'est pas celui attendu. Lesquelles ?\n", + "2. (todo) À l'aide des méthodes `.isnull()` (ou `.isna()`), `.sum()` et `len()`, calculez pour chaque colonne le pourcentage de valeurs non renseignées. Notez quelque part celles qui ont un non remplissage supérieur à 10%. La méthode `sum()` employée sur une série de booléens fait l'addition en considérant que `False == 0` et `True == 1`.\n", + "3. Supprimez du dataframe `shoes_light` les colonnes que vous avez notées dans la question précédente, elles ont trop de valeurs non renseignées.\n", + "4. À l'aide de la fonction `pd.to_datetime()` du dataframe, convertissez le type de la colonne `dateUpdated`.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "5856f3288e1b837c", + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "id object\n", + "name object\n", + "brand object\n", + "dateUpdated object\n", + "colors object\n", + "prices.amountMax float64\n", + "prices.amountMin float64\n", + "prices.merchant object\n", + "dtype: object" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Question 1\n", + "display(shoes_light.dtypes)\n", + "# La colonne dateUpdated est incorrecte" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "8b3be0e49e5edd4e", + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "id 0.00\n", + "name 0.00\n", + "brand 0.00\n", + "dateUpdated 0.00\n", + "colors 73.69\n", + "prices.amountMax 0.00\n", + "prices.amountMin 0.00\n", + "prices.merchant 95.65\n", + "dtype: float64" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Question 2\n", + "# À l'aide des méthodes `.isnull()` (ou `.isna()`), `.sum()` et `len()`, \n", + "# calculez pour chaque colonne le pourcentage de valeurs non renseignées. Notez quelque part celles qui ont un non remplissage supérieur à 10%. La méthode `sum()` employée sur une série de booléens fait l'addition en considérant que `False == 0` et `True == 1`.\n", + "empty_counts = shoes_light.isnull().sum() # Calculer le nombre de valeurs vides pour chaque colonne\n", + "total_length = len(shoes_light)\n", + "percents = empty_counts / total_length * 100\n", + "display(percents)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "3d11033148a17412", + "metadata": { + "collapsed": false + }, + "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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
idnamebranddateUpdatedprices.amountMaxprices.amountMin
0AVpfEf_hLJeJML431ueHNaturalizer Danya Women N/S Open Toe Synthetic...Naturalizer2018-01-29T04:38:43Z55.9955.99
1AVpi74XfLJeJML43qZAcMUK LUKS Womens Jane Suede MoccasinMUK LUKS2018-01-03T05:21:54Z47.0035.25
2AVpi74XfLJeJML43qZAcMUK LUKS Womens Jane Suede MoccasinMUK LUKS2018-01-03T05:21:54Z35.2535.25
3AVpjXyCc1cnluZ0-V-GjMUK LUKS Womens Dawn Suede Scuff SlipperMUK LUKS2018-01-04T11:52:35Z24.7524.75
4AVphGKLPilAPnD_x1NrmMUK LUKS Womens Dawn Suede Scuff SlipperMUK LUKS2018-01-18T03:55:18Z33.0030.39
.....................
9995AWKcCm-RHh53nbDRIh1_Asics Gel-Foundation 12 Running Shoe - Women'sAsics2018-04-06T21:02:04Z119.9589.96
9996AWKcEq-dHh53nbDRIiESAsics Gel-Fortitude 7 Running Shoe - Women'sAsics2018-04-06T21:02:04Z119.9577.97
9997AWKb5jmLYSSHbkXwyzBAKaanas Mesa Hardware Mule - Women'sKaanas2018-04-06T21:02:01Z128.9564.47
9998AWKcEIInYSSHbkXwy0SANike Air Zoom Structure 21 Running Shoe - Narr...Nike2018-04-06T21:01:53Z119.9589.96
9999AWKb6PiKYSSHbkXwyzP-OTZShoes Espadrille Shearling Shoe - Women'sOTZShoes2018-04-06T21:01:50Z99.9549.97
\n", + "

10000 rows × 6 columns

\n", + "
" + ], + "text/plain": [ + " id name brand dateUpdated prices.amountMax prices.amountMin\n", + "0 AVpfEf_hLJeJML431ueH Naturalizer Danya Women N/S Open Toe Synthetic... Naturalizer 2018-01-29T04:38:43Z 55.99 55.99\n", + "1 AVpi74XfLJeJML43qZAc MUK LUKS Womens Jane Suede Moccasin MUK LUKS 2018-01-03T05:21:54Z 47.00 35.25\n", + "2 AVpi74XfLJeJML43qZAc MUK LUKS Womens Jane Suede Moccasin MUK LUKS 2018-01-03T05:21:54Z 35.25 35.25\n", + "3 AVpjXyCc1cnluZ0-V-Gj MUK LUKS Womens Dawn Suede Scuff Slipper MUK LUKS 2018-01-04T11:52:35Z 24.75 24.75\n", + "4 AVphGKLPilAPnD_x1Nrm MUK LUKS Womens Dawn Suede Scuff Slipper MUK LUKS 2018-01-18T03:55:18Z 33.00 30.39\n", + "... ... ... ... ... ... ...\n", + "9995 AWKcCm-RHh53nbDRIh1_ Asics Gel-Foundation 12 Running Shoe - Women's Asics 2018-04-06T21:02:04Z 119.95 89.96\n", + "9996 AWKcEq-dHh53nbDRIiES Asics Gel-Fortitude 7 Running Shoe - Women's Asics 2018-04-06T21:02:04Z 119.95 77.97\n", + "9997 AWKb5jmLYSSHbkXwyzBA Kaanas Mesa Hardware Mule - Women's Kaanas 2018-04-06T21:02:01Z 128.95 64.47\n", + "9998 AWKcEIInYSSHbkXwy0SA Nike Air Zoom Structure 21 Running Shoe - Narr... Nike 2018-04-06T21:01:53Z 119.95 89.96\n", + "9999 AWKb6PiKYSSHbkXwyzP- OTZShoes Espadrille Shearling Shoe - Women's OTZShoes 2018-04-06T21:01:50Z 99.95 49.97\n", + "\n", + "[10000 rows x 6 columns]" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Question 3\n", + "# On a vu que colors et prices.merchant sont mal qualifiées (73% et 95% de valeurs vides)\n", + "# Supprimer ces colonnes\n", + "shoes_light.drop(columns=[\"colors\", \"prices.merchant\"], inplace=True)\n", + "display(shoes_light)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "6bc78f0eab8a090e", + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "id object\n", + "name object\n", + "brand object\n", + "dateUpdated datetime64[ns, UTC]\n", + "prices.amountMax float64\n", + "prices.amountMin float64\n", + "dtype: object" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Question 4\n", + "shoes_light[\"dateUpdated\"] = pd.to_datetime(shoes_light[\"dateUpdated\"])\n", + "display(shoes_light.dtypes)" + ] + }, + { + "cell_type": "markdown", + "id": "97a23a2b56e659b3", + "metadata": { + "collapsed": false + }, + "source": [ + "## Features Modeling\n", + "\n", + "1. Ajoutez au dataframe une nouvelle colonne `prices.amountAverage` calculant la moyenne des colonnes `prices.amountMax` et `prices.amountMin` (via une addition et une division par 2).\n", + "2. Grâce à l'attribut `Series.dt.weekday`, ajoutez au dataframe une nouvelle colonne `dayOfweekUpdated`, extrayant depuis la colonne `dateUpdated` le jour de la semaine où les produits sont mis à jour (un nombre entre 0 et 6).\n" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "c1c2d9b73cf09b13", + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# Question 1\n", + "shoes_light[\"prices.amountAverage\"] = (shoes_light[\"prices.amountMin\"] + shoes_light[\"prices.amountMax\"]) / 2" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "f79a989bd03e4f61", + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# Question 2\n", + "shoes_light[\"dayOfWeekUpdated\"] = shoes_light[\"dateUpdated\"].dt.weekday" + ] + }, + { + "cell_type": "markdown", + "id": "1980ec43bdbabe8", + "metadata": { + "collapsed": false + }, + "source": [ + "## Data Analyse\n", + "\n", + "1. Affichez le prix moyen, écart type, etc. des chaussures avec la méthode `.describe()`.\n", + "2. Y a-t-il de grandes différences de prix en fonction de la marque ? À l'aide des méthodes `groupby()`, `mean()` et `sort_values()`, créez une variable `luxury` contenant les 10 marques les plus chères, puis une variable `low_cost` contenant les 10 marques les moins chères.\n", + "3. Grâce à la méthode `value_counts()`, déterminez le jour de la semaine où les produits sont le plus souvent mis à jour.\n", + "4. **(Optionnel)** Donnez le prix moyen des produits de la marque `easy street` mis à jour un jeudi (jour 3).\n" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "73df484243c117c5", + "metadata": { + "collapsed": false + }, + "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", + "
prices.amountMaxprices.amountMin
count10000.00000010000.000000
mean69.22354451.131209
std19.48728621.267446
min5.8700004.880000
25%59.99000037.490000
50%64.99000049.990000
75%79.99000059.990000
max359.950000359.950000
\n", + "
" + ], + "text/plain": [ + " prices.amountMax prices.amountMin\n", + "count 10000.000000 10000.000000\n", + "mean 69.223544 51.131209\n", + "std 19.487286 21.267446\n", + "min 5.870000 4.880000\n", + "25% 59.990000 37.490000\n", + "50% 64.990000 49.990000\n", + "75% 79.990000 59.990000\n", + "max 359.950000 359.950000" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Question 1\n", + "display(shoes_light.describe())" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "7f1abc9d9f255936", + "metadata": { + "collapsed": false + }, + "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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
prices.amountMaxprices.amountMindayOfWeekUpdatedprices.amountAverage
brand
Unbranded7.12006.4866674.0000006.803333
Soft Ones14.00007.9000001.00000010.950000
Faded Glory14.587512.1750003.75000013.381250
Victoria K.15.880015.8800001.00000015.880000
Genuine Dickies19.970013.8800003.00000016.925000
Earth Spirit19.820016.3800002.50000018.100000
Danskin Now19.740019.7400001.00000019.740000
Riverberry24.990024.9900006.00000024.990000
forever collectibles25.000025.0000002.00000025.000000
Mo Mo28.000022.0933332.33333325.046667
\n", + "
" + ], + "text/plain": [ + " prices.amountMax prices.amountMin dayOfWeekUpdated prices.amountAverage\n", + "brand \n", + "Unbranded 7.1200 6.486667 4.000000 6.803333\n", + "Soft Ones 14.0000 7.900000 1.000000 10.950000\n", + "Faded Glory 14.5875 12.175000 3.750000 13.381250\n", + "Victoria K. 15.8800 15.880000 1.000000 15.880000\n", + "Genuine Dickies 19.9700 13.880000 3.000000 16.925000\n", + "Earth Spirit 19.8200 16.380000 2.500000 18.100000\n", + "Danskin Now 19.7400 19.740000 1.000000 19.740000\n", + "Riverberry 24.9900 24.990000 6.000000 24.990000\n", + "forever collectibles 25.0000 25.000000 2.000000 25.000000\n", + "Mo Mo 28.0000 22.093333 2.333333 25.046667" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
prices.amountMaxprices.amountMindayOfWeekUpdatedprices.amountAverage
brand
Scarpa169.70120.9750000.750145.337500
SoftWalk145.95145.9500002.000145.950000
Lowa199.9592.8357144.000146.392857
Frye165.45133.1950003.500149.322500
La Sportiva152.50146.3750003.875149.437500
Arc'teryx170.00130.3333331.000150.166667
MLB151.24151.2400002.000151.240000
On Footwear159.99159.9900005.000159.990000
Free People178.00178.0000004.000178.000000
Red Wing345.95345.9500003.200345.950000
\n", + "
" + ], + "text/plain": [ + " prices.amountMax prices.amountMin dayOfWeekUpdated prices.amountAverage\n", + "brand \n", + "Scarpa 169.70 120.975000 0.750 145.337500\n", + "SoftWalk 145.95 145.950000 2.000 145.950000\n", + "Lowa 199.95 92.835714 4.000 146.392857\n", + "Frye 165.45 133.195000 3.500 149.322500\n", + "La Sportiva 152.50 146.375000 3.875 149.437500\n", + "Arc'teryx 170.00 130.333333 1.000 150.166667\n", + "MLB 151.24 151.240000 2.000 151.240000\n", + "On Footwear 159.99 159.990000 5.000 159.990000\n", + "Free People 178.00 178.000000 4.000 178.000000\n", + "Red Wing 345.95 345.950000 3.200 345.950000" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Question 2\n", + "# Afficher les 10 marques avec le prix moyen le plus élevé et le plus bas\n", + "average_prices = shoes_light.groupby(by=\"brand\").mean(numeric_only=True)\n", + "ordered_averages = average_prices.sort_values(by=\"prices.amountAverage\")\n", + "\n", + "display(ordered_averages.head(n=10))\n", + "display(ordered_averages.tail(n=10))" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "bae81f6190201b34", + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3\n" + ] + } + ], + "source": [ + "# Question 3\n", + "day_frequencies = shoes_light.value_counts(subset=\"dayOfWeekUpdated\").sort_values(ascending=False)\n", + "print(day_frequencies.index[0])" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "f46f052425a9711f", + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "51.19532786885246\n" + ] + } + ], + "source": [ + "# Question 4\n", + "is_easy_street = shoes_light[\"brand\"] == \"easy street\"\n", + "is_updated_on_thursday = shoes_light[\"dayOfWeekUpdated\"] == 3\n", + "\n", + "easy_street_thursday = shoes_light.loc[is_easy_street & is_updated_on_thursday]\n", + "print(easy_street_thursday[\"prices.amountAverage\"].mean())" + ] + } + ], + "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.11.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} + diff --git a/workshop/pandas-shoes/workshop-notebook.ipynb b/workshop/pandas-shoes/workshop-notebook.ipynb new file mode 100644 index 0000000..2f8ef80 --- /dev/null +++ b/workshop/pandas-shoes/workshop-notebook.ipynb @@ -0,0 +1,302 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Exercice de traitement de données avec Pandas\n", + "\n", + "Tiré du site de Guillaume Dueymes : https://www.guillaumedueymes.com/courses/formation_python/8-pandas-exercice/\n", + "\n", + "Nous allons analyser un data set contenant des informations sur 10 000 paires de chaussures \n", + "vendues sur le site Amazon, avec de nombreuses caractéristiques comme le prix minimal observé, \n", + "le prix maximal observé, les couleurs disponibles, les tailles disponibles, le poids des articles, \n", + "la marque…\n" + ], + "metadata": { + "collapsed": false + }, + "id": "ca962ad386449748" + }, + { + "cell_type": "markdown", + "source": [ + "## Découverte du data set\n", + "\n", + "1. À l’aide de la fonction `read_csv()`, importez entièrement le data set et enregistrez-le dans une variable `shoes`.\n", + "2. Utilisez la méthode `.head()` pour afficher les premières lignes du `DataFrame`.\n", + "3. Il y a plus de 4 colonnes, beaucoup ne sont pas visibles. Afin de toutes les voir lors de l'affichage, utilisez la fonction `pandas.set_option()` pour que `.head()` affiche toutes les colonnes du `DataFrame`. (consultez la documentation de `set_option()`)\n", + "4. On va garder uniquement les colonnes intéressantes. Grâce à la syntaxe de filtrage par colonnes, créez une variable `shoes_light`, comprenant uniquement les colonnes suivantes : `id`, `name`, `brand`, dateUpdated`, `colors`, `prices.amountMax`, `prices.amountMin` et `prices.merchant`. Affichez le `head()` de `shoes_light`.\n" + ], + "metadata": { + "collapsed": false + }, + "id": "2f0fb688fd2b801e" + }, + { + "cell_type": "code", + "execution_count": 1, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requirement already satisfied: pandas in /home/steve/Code/python/.venv/datascience/lib/python3.11/site-packages (2.1.1)\r\n", + "Requirement already satisfied: numpy>=1.23.2 in /home/steve/Code/python/.venv/datascience/lib/python3.11/site-packages (from pandas) (1.26.0)\r\n", + "Requirement already satisfied: python-dateutil>=2.8.2 in /home/steve/Code/python/.venv/datascience/lib/python3.11/site-packages (from pandas) (2.8.2)\r\n", + "Requirement already satisfied: pytz>=2020.1 in /home/steve/Code/python/.venv/datascience/lib/python3.11/site-packages (from pandas) (2023.3.post1)\r\n", + "Requirement already satisfied: tzdata>=2022.1 in /home/steve/Code/python/.venv/datascience/lib/python3.11/site-packages (from pandas) (2023.3)\r\n", + "Requirement already satisfied: six>=1.5 in /home/steve/Code/python/.venv/datascience/lib/python3.11/site-packages (from python-dateutil>=2.8.2->pandas) (1.16.0)\r\n", + "\r\n", + "\u001B[1m[\u001B[0m\u001B[34;49mnotice\u001B[0m\u001B[1;39;49m]\u001B[0m\u001B[39;49m A new release of pip is available: \u001B[0m\u001B[31;49m23.3.1\u001B[0m\u001B[39;49m -> \u001B[0m\u001B[32;49m23.3.2\u001B[0m\r\n", + "\u001B[1m[\u001B[0m\u001B[34;49mnotice\u001B[0m\u001B[1;39;49m]\u001B[0m\u001B[39;49m To update, run: \u001B[0m\u001B[32;49mpip install --upgrade pip\u001B[0m\r\n" + ] + } + ], + "source": [ + "!pip install pandas\n", + "import pandas as pd # noqa" + ], + "metadata": { + "collapsed": false, + "ExecuteTime": { + "end_time": "2023-12-19T09:59:24.292192547Z", + "start_time": "2023-12-19T09:59:21.699160182Z" + } + }, + "id": "fb6b33145b46036b" + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# Question 1\n", + "shoes = pd.read_csv(\"womens-shoes.csv.xz\")" + ], + "metadata": { + "collapsed": false + }, + "id": "1b86b0cd93c36795" + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# Question 2" + ], + "metadata": { + "collapsed": false + }, + "id": "2efcdad9ec4b7583" + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# Question 3" + ], + "metadata": { + "collapsed": false + }, + "id": "2961d6e42459dfa9" + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# Question 4" + ], + "metadata": { + "collapsed": false + }, + "id": "6ee0a6019e90db1b" + }, + { + "cell_type": "markdown", + "source": [ + "## Data Cleaning\n", + "\n", + "1. À l'aide de l'attribut `.dtypes` du dataframe, regardez attentivement les types de chaque colonne. Certaines ont un type qui n'est pas celui attendu. Lesquelles ?\n", + "2. À l'aide des méthodes `.isnull()` (ou `.isna()`), `.sum()` et `len()`, calculez pour chaque colonne le pourcentage de valeurs non renseignées. Notez quelque part celles qui ont un non remplissage supérieur à 10%. La méthode `sum()` employée sur une série de booléens fait l'addition en considérant que `False == 0` et `True == 1`.\n", + "3. Supprimez du dataframe `shoes_light` les colonnes que vous avez notées dans la question précédente, elles ont trop de valeurs non renseignées.\n", + "4. À l'aide de la méthode `.to_datetime()` du dataframe, convertissez le type de la colonne `dateUpdated`.\n" + ], + "metadata": { + "collapsed": false + }, + "id": "865055e156e3a14c" + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# Question 1" + ], + "metadata": { + "collapsed": false + }, + "id": "5856f3288e1b837c" + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# Question 2" + ], + "metadata": { + "collapsed": false + }, + "id": "8b3be0e49e5edd4e" + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# Question 3" + ], + "metadata": { + "collapsed": false + }, + "id": "3d11033148a17412" + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# Question 4" + ], + "metadata": { + "collapsed": false + }, + "id": "6bc78f0eab8a090e" + }, + { + "cell_type": "markdown", + "source": [ + "## Features Modeling\n", + "\n", + "1. Ajoutez au dataframe une nouvelle colonne `prices.amountAverage` calculant la moyenne des colonnes `prices.amountMax` et `prices.amountMin` (via une addition et une division par 2).\n", + "2. Grâce à l'attribut `Series.dt.weekday`, ajoutez au dataframe une nouvelle colonne `dayOfweekUpdated`, extrayant depuis la colonne `dateUpdated` le jour de la semaine où les produits sont mis à jour (un nombre entre 0 et 6).\n" + ], + "metadata": { + "collapsed": false + }, + "id": "97a23a2b56e659b3" + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# Question 1" + ], + "metadata": { + "collapsed": false + }, + "id": "c1c2d9b73cf09b13" + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# Question 2" + ], + "metadata": { + "collapsed": false + }, + "id": "f79a989bd03e4f61" + }, + { + "cell_type": "markdown", + "source": [ + "## Data Analyse\n", + "\n", + "1. Affichez le prix moyen, écart type, etc. des chaussures avec la méthode `.describe()`.\n", + "2. Y a-t-il de grandes différences de prix en fonction de la marque ? À l'aide des méthodes `groupby()`, `mean()` et `sort_values()`, créez une variable `luxury` contenant les 10 marques les plus chères, puis une variable `low_cost` contenant les 10 marques les moins chères.\n", + "3. Grâce à la méthode `value_counts()`, déterminez le jour de la semaine où les produits sont le plus souvent mis à jour.\n", + "4. **(Optionnel)** Donnez le prix moyen des produits de la marque `easy street` mis à jour un jeudi (jour 3).\n" + ], + "metadata": { + "collapsed": false + }, + "id": "1980ec43bdbabe8" + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# Question 1" + ], + "metadata": { + "collapsed": false + }, + "id": "73df484243c117c5" + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# Question 2" + ], + "metadata": { + "collapsed": false + }, + "id": "7f1abc9d9f255936" + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# Question 3" + ], + "metadata": { + "collapsed": false + }, + "id": "bae81f6190201b34" + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "# Question 4" + ], + "metadata": { + "collapsed": false + }, + "id": "f46f052425a9711f" + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}