diff --git a/.DS_Store b/.DS_Store index d65e142..1b73774 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/learn_python_data_sci_track.ipynb b/DS_track/learn_python_data_sci_track.ipynb similarity index 100% rename from learn_python_data_sci_track.ipynb rename to DS_track/learn_python_data_sci_track.ipynb diff --git a/learn_python_data_sci_track_part1.ipynb b/DS_track/learn_python_data_sci_track_part1.ipynb similarity index 100% rename from learn_python_data_sci_track_part1.ipynb rename to DS_track/learn_python_data_sci_track_part1.ipynb diff --git a/learn_python_data_sci_track_part2.ipynb b/DS_track/learn_python_data_sci_track_part2.ipynb similarity index 98% rename from learn_python_data_sci_track_part2.ipynb rename to DS_track/learn_python_data_sci_track_part2.ipynb index 99be730..68ac33a 100644 --- a/learn_python_data_sci_track_part2.ipynb +++ b/DS_track/learn_python_data_sci_track_part2.ipynb @@ -1,26 +1,10 @@ { - "nbformat": 4, - "nbformat_minor": 0, - "metadata": { - "colab": { - "provenance": [], - "authorship_tag": "ABX9TyNEVyRNtSXPLUtt/75byQlH", - "include_colab_link": true - }, - "kernelspec": { - "name": "python3", - "display_name": "Python 3" - }, - "language_info": { - "name": "python" - } - }, "cells": [ { "cell_type": "markdown", "metadata": { - "id": "view-in-github", - "colab_type": "text" + "colab_type": "text", + "id": "view-in-github" }, "source": [ "\"Open" @@ -39,10 +23,7 @@ }, { "cell_type": "code", - "source": [ - "df = pd.read_csv('/content/train.csv')\n", - "df.head()" - ], + "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -51,33 +32,9 @@ "id": "OgREFBzCElX7", "outputId": "fcdee8b1-22c9-4d74-e2a8-93a05e20c6e7" }, - "execution_count": null, "outputs": [ { - "output_type": "execute_result", "data": { - "text/plain": [ - " PassengerId Survived Pclass \\\n", - "0 1 0 3 \n", - "1 2 1 1 \n", - "2 3 1 3 \n", - "3 4 1 1 \n", - "4 5 0 3 \n", - "\n", - " Name Sex Age SibSp \\\n", - "0 Braund, Mr. Owen Harris male 22.0 1 \n", - "1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 \n", - "2 Heikkinen, Miss. Laina female 26.0 0 \n", - "3 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 \n", - "4 Allen, Mr. William Henry male 35.0 0 \n", - "\n", - " Parch Ticket Fare Cabin Embarked \n", - "0 0 A/5 21171 7.2500 NaN S \n", - "1 0 PC 17599 71.2833 C85 C \n", - "2 0 STON/O2. 3101282 7.9250 NaN S \n", - "3 0 113803 53.1000 C123 S \n", - "4 0 373450 8.0500 NaN S " - ], "text/html": [ "\n", "
\n", @@ -269,18 +226,43 @@ "
\n", " \n", " " + ], + "text/plain": [ + " PassengerId Survived Pclass \\\n", + "0 1 0 3 \n", + "1 2 1 1 \n", + "2 3 1 3 \n", + "3 4 1 1 \n", + "4 5 0 3 \n", + "\n", + " Name Sex Age SibSp \\\n", + "0 Braund, Mr. Owen Harris male 22.0 1 \n", + "1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 \n", + "2 Heikkinen, Miss. Laina female 26.0 0 \n", + "3 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 \n", + "4 Allen, Mr. William Henry male 35.0 0 \n", + "\n", + " Parch Ticket Fare Cabin Embarked \n", + "0 0 A/5 21171 7.2500 NaN S \n", + "1 0 PC 17599 71.2833 C85 C \n", + "2 0 STON/O2. 3101282 7.9250 NaN S \n", + "3 0 113803 53.1000 C123 S \n", + "4 0 373450 8.0500 NaN S " ] }, + "execution_count": 3, "metadata": {}, - "execution_count": 3 + "output_type": "execute_result" } + ], + "source": [ + "df = pd.read_csv('/content/train.csv')\n", + "df.head()" ] }, { "cell_type": "code", - "source": [ - "df.info()" - ], + "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -288,11 +270,10 @@ "id": "oIYgMNxVFvil", "outputId": "b4b2de4b-a69c-4ac4-9555-4bd19121f964" }, - "execution_count": null, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "\n", "RangeIndex: 891 entries, 0 to 890\n", @@ -315,10 +296,16 @@ "memory usage: 83.7+ KB\n" ] } + ], + "source": [ + "df.info()" ] }, { "cell_type": "markdown", + "metadata": { + "id": "WgxSATNNF3a-" + }, "source": [ "\n", "* Survived - спасся ли пассажир (да- 1 или нет - 0)\n", @@ -326,16 +313,11 @@ "* SibSp - муж/жена/братья/сестры на борту\n", "* Parch - родители/дети на борту\n", "* Embarked - порт посадки (С - Cherbourg, S - Southampton, Q = Queenstown)" - ], - "metadata": { - "id": "WgxSATNNF3a-" - } + ] }, { "cell_type": "code", - "source": [ - "df.loc[df['Embarked'].isnull()]" - ], + "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -344,20 +326,9 @@ "id": "o1ezLmaqfDvK", "outputId": "4488c421-7f10-4c0a-d098-4bd8d2f14e62" }, - "execution_count": null, "outputs": [ { - "output_type": "execute_result", "data": { - "text/plain": [ - " PassengerId Survived Pclass Name \\\n", - "61 62 1 1 Icard, Miss. Amelie \n", - "829 830 1 1 Stone, Mrs. George Nelson (Martha Evelyn) \n", - "\n", - " Sex Age SibSp Parch Ticket Fare Cabin Embarked \n", - "61 female 38.0 0 0 113572 80.0 B28 NaN \n", - "829 female 62.0 0 0 113572 80.0 B28 NaN " - ], "text/html": [ "\n", "
\n", @@ -504,86 +475,79 @@ "
\n", " \n", " " + ], + "text/plain": [ + " PassengerId Survived Pclass Name \\\n", + "61 62 1 1 Icard, Miss. Amelie \n", + "829 830 1 1 Stone, Mrs. George Nelson (Martha Evelyn) \n", + "\n", + " Sex Age SibSp Parch Ticket Fare Cabin Embarked \n", + "61 female 38.0 0 0 113572 80.0 B28 NaN \n", + "829 female 62.0 0 0 113572 80.0 B28 NaN " ] }, + "execution_count": 5, "metadata": {}, - "execution_count": 5 + "output_type": "execute_result" } + ], + "source": [ + "df.loc[df['Embarked'].isnull()]" ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "y1elKpa0GF7o" + }, + "outputs": [], "source": [ "embarked = df.groupby('Embarked').count()['PassengerId']\n", "embarked_max = embarked[embarked == embarked.max()].index[0]\n", "df.loc[df['Embarked'].isnull(), 'Embarked'] = embarked_max" - ], - "metadata": { - "id": "y1elKpa0GF7o" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "code", - "source": [ - "df['Age'].median()" - ], + "execution_count": null, "metadata": { - "id": "KhcgcXsCfb_8", "colab": { "base_uri": "https://localhost:8080/" }, + "id": "KhcgcXsCfb_8", "outputId": "fc4c7559-6ed4-4bb2-d02f-690f3129fca8" }, - "execution_count": null, "outputs": [ { - "output_type": "execute_result", "data": { "text/plain": [ "28.0" ] }, + "execution_count": 7, "metadata": {}, - "execution_count": 7 + "output_type": "execute_result" } + ], + "source": [ + "df['Age'].median()" ] }, { "cell_type": "code", - "source": [ - "df['Name'].str.extract('([A-Za-z]+)\\.')" - ], + "execution_count": null, "metadata": { - "id": "u3HOHD6aGM5n", "colab": { "base_uri": "https://localhost:8080/", "height": 424 }, + "id": "u3HOHD6aGM5n", "outputId": "936544b5-a3b0-4b63-fec6-0b030e8af38d" }, - "execution_count": null, "outputs": [ { - "output_type": "execute_result", "data": { - "text/plain": [ - " 0\n", - "0 Mr\n", - "1 Mrs\n", - "2 Miss\n", - "3 Mrs\n", - "4 Mr\n", - ".. ...\n", - "886 Rev\n", - "887 Miss\n", - "888 Miss\n", - "889 Mr\n", - "890 Mr\n", - "\n", - "[891 rows x 1 columns]" - ], "text/html": [ "\n", "
\n", @@ -734,19 +698,36 @@ "
\n", " \n", " " + ], + "text/plain": [ + " 0\n", + "0 Mr\n", + "1 Mrs\n", + "2 Miss\n", + "3 Mrs\n", + "4 Mr\n", + ".. ...\n", + "886 Rev\n", + "887 Miss\n", + "888 Miss\n", + "889 Mr\n", + "890 Mr\n", + "\n", + "[891 rows x 1 columns]" ] }, + "execution_count": 8, "metadata": {}, - "execution_count": 8 + "output_type": "execute_result" } + ], + "source": [ + "df['Name'].str.extract('([A-Za-z]+)\\.')" ] }, { "cell_type": "code", - "source": [ - "df['Title'] = df['Name'].str.extract('([A-Za-z]+)\\.')\n", - "df['Title'].unique()" - ], + "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -754,10 +735,8 @@ "id": "t2qMiz3xRsC1", "outputId": "43a57d50-205c-4ea5-dd46-f3b3590badb9" }, - "execution_count": null, "outputs": [ { - "output_type": "execute_result", "data": { "text/plain": [ "array(['Mr', 'Mrs', 'Miss', 'Master', 'Don', 'Rev', 'Dr', 'Mme', 'Ms',\n", @@ -765,22 +744,19 @@ " 'Jonkheer'], dtype=object)" ] }, + "execution_count": 9, "metadata": {}, - "execution_count": 9 + "output_type": "execute_result" } + ], + "source": [ + "df['Title'] = df['Name'].str.extract('([A-Za-z]+)\\.')\n", + "df['Title'].unique()" ] }, { "cell_type": "code", - "source": [ - "mapping = {'Don': 'RoyaltyM', 'Mme': 'Miss', 'Ms': 'Miss',\n", - " 'Major': 'Officer', 'Lady': 'RoyaltyF', 'Sir': 'RoyaltyM', \n", - " 'Mlle': 'Miss', 'Col': 'Officer', 'Capt': 'Officer',\n", - " 'Countess': 'RoyaltyF', 'Jonkheer': 'RoyaltyM'}\n", - " \n", - "df.replace({'Title': mapping}, inplace=True)\n", - "df['Title'].unique()" - ], + "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -788,27 +764,32 @@ "id": "GRuB_bI0RwKE", "outputId": "0bb920de-3474-45ae-a23f-203907b576d8" }, - "execution_count": null, "outputs": [ { - "output_type": "execute_result", "data": { "text/plain": [ "array(['Mr', 'Mrs', 'Miss', 'Master', 'RoyaltyM', 'Rev', 'Dr', 'Officer',\n", " 'RoyaltyF'], dtype=object)" ] }, + "execution_count": 10, "metadata": {}, - "execution_count": 10 + "output_type": "execute_result" } + ], + "source": [ + "mapping = {'Don': 'RoyaltyM', 'Mme': 'Miss', 'Ms': 'Miss',\n", + " 'Major': 'Officer', 'Lady': 'RoyaltyF', 'Sir': 'RoyaltyM', \n", + " 'Mlle': 'Miss', 'Col': 'Officer', 'Capt': 'Officer',\n", + " 'Countess': 'RoyaltyF', 'Jonkheer': 'RoyaltyM'}\n", + " \n", + "df.replace({'Title': mapping}, inplace=True)\n", + "df['Title'].unique()" ] }, { "cell_type": "code", - "source": [ - "age_med = df.groupby('Title')['Age'].median()\n", - "age_med" - ], + "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -816,10 +797,8 @@ "id": "2ThrjmNFR3Vb", "outputId": "c9d16e7e-a206-4df4-b47f-088c1661b8db" }, - "execution_count": null, "outputs": [ { - "output_type": "execute_result", "data": { "text/plain": [ "Title\n", @@ -835,17 +814,19 @@ "Name: Age, dtype: float64" ] }, + "execution_count": 11, "metadata": {}, - "execution_count": 11 + "output_type": "execute_result" } + ], + "source": [ + "age_med = df.groupby('Title')['Age'].median()\n", + "age_med" ] }, { "cell_type": "code", - "source": [ - "for t, a in age_med.iteritems():\n", - " print(f'{t} {a}')" - ], + "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -853,11 +834,10 @@ "id": "53sLkBO8STLd", "outputId": "82462319-a3b7-4870-b668-b91389a078cc" }, - "execution_count": null, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "Dr 46.5\n", "Master 3.5\n", @@ -870,27 +850,29 @@ "RoyaltyM 40.0\n" ] } + ], + "source": [ + "for t, a in age_med.iteritems():\n", + " print(f'{t} {a}')" ] }, { "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "ixm_5r9LSFlt" + }, + "outputs": [], "source": [ "for title, age in age_med.iteritems():\n", " filter_age = df['Age'].isnull()\n", " filter_title = df['Title'] == title\n", " df.loc[filter_age & filter_title, 'Age'] = age" - ], - "metadata": { - "id": "ixm_5r9LSFlt" - }, - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "code", - "source": [ - "df.info()" - ], + "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -898,11 +880,10 @@ "id": "jZEjYJbJTlLu", "outputId": "d31f22fe-9420-4ec6-d869-abd86d0e8b25" }, - "execution_count": null, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "\n", "RangeIndex: 891 entries, 0 to 890\n", @@ -926,14 +907,14 @@ "memory usage: 90.6+ KB\n" ] } + ], + "source": [ + "df.info()" ] }, { "cell_type": "code", - "source": [ - "df.pivot_table(values='PassengerId', index='Pclass',\n", - " columns='Survived', aggfunc='count')" - ], + "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -942,18 +923,9 @@ "id": "GPJ1bCWZTono", "outputId": "c6dcb574-5881-4cc7-8572-2a2a5d418554" }, - "execution_count": null, "outputs": [ { - "output_type": "execute_result", "data": { - "text/plain": [ - "Survived 0 1\n", - "Pclass \n", - "1 80 136\n", - "2 97 87\n", - "3 372 119" - ], "text/html": [ "\n", "
\n", @@ -1080,24 +1052,28 @@ "
\n", " \n", " " + ], + "text/plain": [ + "Survived 0 1\n", + "Pclass \n", + "1 80 136\n", + "2 97 87\n", + "3 372 119" ] }, + "execution_count": 15, "metadata": {}, - "execution_count": 15 + "output_type": "execute_result" } + ], + "source": [ + "df.pivot_table(values='PassengerId', index='Pclass',\n", + " columns='Survived', aggfunc='count')" ] }, { "cell_type": "code", - "source": [ - "# df['Sex'] доп задание из видео\n", - "\n", - "mapping = {'male': 0, 'female':1}\n", - " \n", - "df.replace({'Sex': mapping}, inplace=True)\n", - "df['Sex'].unique()\n", - "df['Sex']" - ], + "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -1105,10 +1081,8 @@ "id": "eRmkTD2WTr9a", "outputId": "d878949b-5b45-4ce6-ba17-7f5d47e7da9c" }, - "execution_count": null, "outputs": [ { - "output_type": "execute_result", "data": { "text/plain": [ "0 0\n", @@ -1125,17 +1099,24 @@ "Name: Sex, Length: 891, dtype: int64" ] }, + "execution_count": 16, "metadata": {}, - "execution_count": 16 + "output_type": "execute_result" } + ], + "source": [ + "# df['Sex'] доп задание из видео\n", + "\n", + "mapping = {'male': 0, 'female':1}\n", + " \n", + "df.replace({'Sex': mapping}, inplace=True)\n", + "df['Sex'].unique()\n", + "df['Sex']" ] }, { "cell_type": "code", - "source": [ - "df.pivot_table(values='PassengerId', index='Pclass',\n", - " columns='Survived', aggfunc='count')" - ], + "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -1144,18 +1125,9 @@ "id": "lPDErHQnhJre", "outputId": "d9f777d3-3a93-44a3-873d-3419e21eb10d" }, - "execution_count": null, "outputs": [ { - "output_type": "execute_result", "data": { - "text/plain": [ - "Survived 0 1\n", - "Pclass \n", - "1 80 136\n", - "2 97 87\n", - "3 372 119" - ], "text/html": [ "\n", "
\n", @@ -1282,103 +1254,112 @@ "
\n", " \n", " " + ], + "text/plain": [ + "Survived 0 1\n", + "Pclass \n", + "1 80 136\n", + "2 97 87\n", + "3 372 119" ] }, + "execution_count": 17, "metadata": {}, - "execution_count": 17 + "output_type": "execute_result" } + ], + "source": [ + "df.pivot_table(values='PassengerId', index='Pclass',\n", + " columns='Survived', aggfunc='count')" ] }, { "cell_type": "code", - "source": [ - "surv_pclass = df.pivot_table(values='PassengerId', index='Pclass',\n", - " columns='Survived', aggfunc='count')\n", - "\n", - "surv_pclass.plot(kind='bar', stacked=True)" - ], + "execution_count": null, "metadata": { - "id": "H5m4BO9chgfz", - "outputId": "b46e30a3-b411-45bf-de15-887ef0ad148b", "colab": { "base_uri": "https://localhost:8080/", "height": 294 - } + }, + "id": "H5m4BO9chgfz", + "outputId": "b46e30a3-b411-45bf-de15-887ef0ad148b" }, - "execution_count": null, "outputs": [ { - "output_type": "execute_result", "data": { "text/plain": [ "" ] }, + "execution_count": 18, "metadata": {}, - "execution_count": 18 + "output_type": "execute_result" }, { - "output_type": "display_data", "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXcAAAEDCAYAAADOc0QpAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAAsTAAALEwEAmpwYAAATtElEQVR4nO3df5BV5Z3n8fd3aAQTDEToUKab0GQgmYBgJzZG102K1ZloiIX5Q2mdKcRohSRqDVvZqYlrZVe2atwy2dRmjUklQxUZSGJE1ElBMSlniObX5Aem2yBGjQX+yNAUUUBDFg2D4Hf/6APbgYbupm/3pZ9+v6pu3XOe57nnfE/dqg+H555zOjITSVJZ/qTeBUiSas9wl6QCGe6SVCDDXZIKZLhLUoEMd0kqUEO9CwCYMmVKtrS01LsMSRpROjs792RmY299p0W4t7S00NHRUe8yJGlEiYjfnKjPaRlJKlC/wj0iXoiIJyJiS0R0VG1nR8SmiNhWvb+1ao+I+FJEbI+IrRHxvqE8AEnS8QZy5v6fMrM1M9uq9VuBhzNzFvBwtQ7wYWBW9VoGfLVWxUqS+mcwc+5XAguq5TXAD4DPVO3fyO6H1vw8IiZFxDmZuWsgG3/99dfp6uriwIEDgyjx9DB+/Hiam5sZO3ZsvUuRNEr0N9wT+JeISODvM3MlMLVHYP8WmFotNwE7eny2q2obULh3dXVx1lln0dLSQkQM5KOnlcxk7969dHV1MWPGjHqXI2mU6G+4/8fM3BkRbwM2RcSve3ZmZlbB328RsYzuaRve8Y53HNd/4MCBER/sABHB5MmT2b17d71LkTSK9GvOPTN3Vu8vAd8BLgBejIhzAKr3l6rhO4FpPT7eXLUdu82VmdmWmW2Njb1epjnig/2IUo5D0sjRZ7hHxJsj4qwjy8CHgF8BG4Cl1bClwPpqeQNwXXXVzIXAvoHOt5/MHXfcwZw5c5g3bx6tra1s3rx50NvcsGEDd955Zw2qgwkTJtRkO5I0GP2ZlpkKfKc6+2wAvp2ZD0XEL4B1EXEj8BtgcTX+u8BCYDvwGvCxWhX7s5/9jI0bN/LYY48xbtw49uzZw8GDB/v12UOHDtHQ0PvhLlq0iEWLFtWqTEknsmJivSsYWiv21buCo/o8c8/M5zLzvOo1JzPvqNr3ZualmTkrM/88M1+u2jMzb87MP83MuZlZs1tPd+3axZQpUxg3bhwAU6ZM4e1vfzstLS3s2bMHgI6ODhYsWADAihUrWLJkCRdffDFLlizhwgsv5Mknnzy6vQULFtDR0cHq1au55ZZb2LdvH9OnT+eNN94A4NVXX2XatGm8/vrrPPvss1x++eWcf/75fOADH+DXv+7+2eH555/noosuYu7cuXz2s5+t1aFK0qCMqDtUP/ShD7Fjxw7e9a53cdNNN/HDH/6wz8889dRTfO973+Pee++lvb2ddevWAd3/UOzatYu2trajYydOnEhra+vR7W7cuJHLLruMsWPHsmzZMu6++246Ozv5whe+wE033QTA8uXL+dSnPsUTTzzBOeecMwRHLUkDN6LCfcKECXR2drJy5UoaGxtpb29n9erVJ/3MokWLOPPMMwFYvHgxDzzwAADr1q3jqquuOm58e3s79913HwBr166lvb2d/fv389Of/pSrr76a1tZWPvGJT7BrV/fPCD/5yU+49tprAViyZEmtDlWSBuW0eHDYQIwZM4YFCxawYMEC5s6dy5o1a2hoaDg6lXLsTU9vfvObjy43NTUxefJktm7dyn333cfXvva147a/aNEibrvtNl5++WU6Ozu55JJLePXVV5k0aRJbtmzptSavhpF0uhlRZ+7PPPMM27ZtO7q+ZcsWpk+fTktLC52dnQA8+OCDJ91Ge3s7n//859m3bx/z5s07rn/ChAnMnz+f5cuXc8UVVzBmzBje8pa3MGPGDO6//36g+8akxx9/HICLL76YtWvXAnDPPffU5DglabBGVLjv37+fpUuXMnv2bObNm8dTTz3FihUruP3221m+fDltbW2MGTPmpNu46qqrWLt2LYsXLz7hmPb2dr71rW/R3t5+tO2ee+5h1apVnHfeecyZM4f167uv/Lzrrrv4yle+wty5c9m587jL+SWpLqL7ETD11dbWlsc+z/3pp5/mPe95T50qqr3Sjkc6JV4KWVMR0dnjYY5/ZESduUuS+sdwl6QCGe6SVCDDXZIKZLhLUoEMd0kqkOE+QA899BDvfve7mTlzZs0eEyxJtTbiHj/QU8ut/1TT7b1w50dO2n/48GFuvvlmNm3aRHNzM/Pnz2fRokXMnj27pnVI0mB55j4Ajz76KDNnzuSd73wnZ5xxBtdcc83RO1Ul6XRiuA/Azp07mTbt//8FwebmZh85IOm0ZLhLUoEM9wFoampi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", "text/plain": [ "
" - ], - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXcAAAEDCAYAAADOc0QpAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAAsTAAALEwEAmpwYAAATtElEQVR4nO3df5BV5Z3n8fd3aAQTDEToUKab0GQgmYBgJzZG102K1ZloiIX5Q2mdKcRohSRqDVvZqYlrZVe2atwy2dRmjUklQxUZSGJE1ElBMSlniObX5Aem2yBGjQX+yNAUUUBDFg2D4Hf/6APbgYbupm/3pZ9+v6pu3XOe57nnfE/dqg+H555zOjITSVJZ/qTeBUiSas9wl6QCGe6SVCDDXZIKZLhLUoEMd0kqUEO9CwCYMmVKtrS01LsMSRpROjs792RmY299p0W4t7S00NHRUe8yJGlEiYjfnKjPaRlJKlC/wj0iXoiIJyJiS0R0VG1nR8SmiNhWvb+1ao+I+FJEbI+IrRHxvqE8AEnS8QZy5v6fMrM1M9uq9VuBhzNzFvBwtQ7wYWBW9VoGfLVWxUqS+mcwc+5XAguq5TXAD4DPVO3fyO6H1vw8IiZFxDmZuWsgG3/99dfp6uriwIEDgyjx9DB+/Hiam5sZO3ZsvUuRNEr0N9wT+JeISODvM3MlMLVHYP8WmFotNwE7eny2q2obULh3dXVx1lln0dLSQkQM5KOnlcxk7969dHV1MWPGjHqXI2mU6G+4/8fM3BkRbwM2RcSve3ZmZlbB328RsYzuaRve8Y53HNd/4MCBER/sABHB5MmT2b17d71LkTSK9GvOPTN3Vu8vAd8BLgBejIhzAKr3l6rhO4FpPT7eXLUdu82VmdmWmW2Njb1epjnig/2IUo5D0sjRZ7hHxJsj4qwjy8CHgF8BG4Cl1bClwPpqeQNwXXXVzIXAvoHOt5/MHXfcwZw5c5g3bx6tra1s3rx50NvcsGEDd955Zw2qgwkTJtRkO5I0GP2ZlpkKfKc6+2wAvp2ZD0XEL4B1EXEj8BtgcTX+u8BCYDvwGvCxWhX7s5/9jI0bN/LYY48xbtw49uzZw8GDB/v12UOHDtHQ0PvhLlq0iEWLFtWqTEknsmJivSsYWiv21buCo/o8c8/M5zLzvOo1JzPvqNr3ZualmTkrM/88M1+u2jMzb87MP83MuZlZs1tPd+3axZQpUxg3bhwAU6ZM4e1vfzstLS3s2bMHgI6ODhYsWADAihUrWLJkCRdffDFLlizhwgsv5Mknnzy6vQULFtDR0cHq1au55ZZb2LdvH9OnT+eNN94A4NVXX2XatGm8/vrrPPvss1x++eWcf/75fOADH+DXv+7+2eH555/noosuYu7cuXz2s5+t1aFK0qCMqDtUP/ShD7Fjxw7e9a53cdNNN/HDH/6wz8889dRTfO973+Pee++lvb2ddevWAd3/UOzatYu2trajYydOnEhra+vR7W7cuJHLLruMsWPHsmzZMu6++246Ozv5whe+wE033QTA8uXL+dSnPsUTTzzBOeecMwRHLUkDN6LCfcKECXR2drJy5UoaGxtpb29n9erVJ/3MokWLOPPMMwFYvHgxDzzwAADr1q3jqquuOm58e3s79913HwBr166lvb2d/fv389Of/pSrr76a1tZWPvGJT7BrV/fPCD/5yU+49tprAViyZEmtDlWSBuW0eHDYQIwZM4YFCxawYMEC5s6dy5o1a2hoaDg6lXLsTU9vfvObjy43NTUxefJktm7dyn333cfXvva147a/aNEibrvtNl5++WU6Ozu55JJLePXVV5k0aRJbtmzptSavhpF0uhlRZ+7PPPMM27ZtO7q+ZcsWpk+fTktLC52dnQA8+OCDJ91Ge3s7n//859m3bx/z5s07rn/ChAnMnz+f5cuXc8UVVzBmzBje8pa3MGPGDO6//36g+8akxx9/HICLL76YtWvXAnDPPffU5DglabBGVLjv37+fpUuXMnv2bObNm8dTTz3FihUruP3221m+fDltbW2MGTPmpNu46qqrWLt2LYsXLz7hmPb2dr71rW/R3t5+tO2ee+5h1apVnHfeecyZM4f167uv/Lzrrrv4yle+wty5c9m587jL+SWpLqL7ETD11dbWlsc+z/3pp5/mPe95T50qqr3Sjkc6JV4KWVMR0dnjYY5/ZESduUuS+sdwl6QCGe6SVCDDXZIKZLhLUoEMd0kqkOE+QA899BDvfve7mTlzZs0eEyxJtTbiHj/QU8ut/1TT7b1w50dO2n/48GFuvvlmNm3aRHNzM/Pnz2fRokXMnj27pnVI0mB55j4Ajz76KDNnzuSd73wnZ5xxBtdcc83RO1Ul6XRiuA/Azp07mTbt//8FwebmZh85IOm0ZLhLUoEM9wFoampi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\n" + ] }, "metadata": { "needs_background": "light" - } + }, + "output_type": "display_data" } + ], + "source": [ + "surv_pclass = df.pivot_table(values='PassengerId', index='Pclass',\n", + " columns='Survived', aggfunc='count')\n", + "\n", + "surv_pclass.plot(kind='bar', stacked=True)" ] }, { "cell_type": "code", - "source": [ - "surv_title = df.pivot_table(values='PassengerId', index='Title',\n", - " columns='Survived', aggfunc='count')\n", - "\n", - "surv_title.plot(kind='bar', figsize=(20,20))" - ], + "execution_count": 20, "metadata": { - "id": "opFXVRy0hmPt", "colab": { "base_uri": "https://localhost:8080/", "height": 1000 }, + "id": "opFXVRy0hmPt", "outputId": "3be79001-19f8-47b9-c593-a4db040075cb" }, - "execution_count": 20, "outputs": [ { - "output_type": "execute_result", "data": { "text/plain": [ "" ] }, + "execution_count": 20, "metadata": {}, - "execution_count": 20 + "output_type": "execute_result" }, { - "output_type": "display_data", "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABIQAAASQCAYAAACNlZS7AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAAsTAAALEwEAmpwYAAA6G0lEQVR4nO3df7TldX3f+9fbGWRsFIhAEmWIg/FXQJAkQ6LR5FJcxp8ZTRcyUotEbUmiNLTm3pakWUtMm15MbazRNCkJRmwQxB+3EDRa4697NVadUUTFuEDRxcwiFVCJYIyCn/vH3oPjMMzPs8935rwfj7XOmr2/e5/zfZ+1Pcz4PN/PZ9cYIwAAAAD0cb+pBwAAAABgeQlCAAAAAM0IQgAAAADNCEIAAAAAzQhCAAAAAM0IQgAAAADNrJ56gCQ56qijxrp166YeAwAAAGDF2Lx5861jjKN39tgBEYTWrVuXTZs2TT0GAAAAwIpRVV++r8csGQMAAABoRhACAAAAaEYQAgAAAGjmgNhDCAAAAGA5fec738mWLVvyrW99a+pR9tuaNWuydu3aHHLIIXv8OYIQAAAA0M6WLVvyoAc9KOvWrUtVTT3OPhtj5LbbbsuWLVty3HHH7fHnWTIGAAAAtPOtb30rRx555EEdg5KkqnLkkUfu9ZVOghAAAADQ0sEeg7bZl+9DEAIAAACY+93f/d2ccMIJOemkk3LyySfnox/96H5/zauuuioXXnjhEkyXPPCBD1ySr2MPIQAAAIAkH/nIR3L11VfnE5/4RA499NDceuut+fa3v71Hn3vXXXdl9eqdZ5YNGzZkw4YNSznqfnOFEAAAAECSm2++OUcddVQOPfTQJMlRRx2Vhz70oVm3bl1uvfXWJMmmTZty6qmnJkkuuOCCnHXWWXniE5+Ys846K49//OPz2c9+9p6vd+qpp2bTpk15wxvekHPPPTe33357Hvawh+W73/1ukuTOO+/Msccem+985zv5whe+kKc97Wn5qZ/6qfzcz/1c/uZv/iZJcuONN+YJT3hCTjzxxPz2b//2kn2vghAAAABAkl/4hV/ITTfdlEc96lF5yUtekg9+8IO7/Zzrrrsuf/VXf5XLLrssGzduzBVXXJFkFpduvvnmrF+//p7nHn744Tn55JPv+bpXX311nvrUp+aQQw7JOeeck9e+9rXZvHlzXvWqV+UlL3lJkuS8887Lr/3ar+XTn/50HvKQhyzZ9yoIAQAAAGS2P8/mzZtz0UUX5eijj87GjRvzhje8YZefs2HDhjzgAQ9Ikpxxxhl561vfmiS54oorcvrpp9/r+Rs3bsyb3/zmJMnll1+ejRs35o477shf//Vf57nPfW5OPvnk/Mqv/EpuvvnmJMmHP/zhnHnmmUmSs846a6m+VXsIAQAAAGyzatWqnHrqqTn11FNz4okn5pJLLsnq1avvWea149u7/8AP/MA9t4855pgceeSRufbaa/PmN785f/zHf3yvr79hw4b81m/9Vr761a9m8+bNOe2003LnnXfmiCOOyDXXXLPTmRbxbmiuEAIAAABI8vnPfz7XX3/9PfevueaaPOxhD8u6deuyefPmJMnb3va2XX6NjRs35vd+7/dy++2356STTrrX4w984ANzyimn5LzzzsuznvWsrFq1KocddliOO+64vOUtb0mSjDHyqU99KknyxCc+MZdffnmS5NJLL12S7zMRhAAAAACSJHfccUfOPvvsHH/88TnppJNy3XXX5YILLsjLX/7ynHfeeVm/fn1WrVq1y69x+umn5/LLL88ZZ5xxn8/ZuHFj/vzP/zwbN26859ill16aiy++OI973ONywgkn5Morr0ySvOY1r8kf/uEf5sQTT8zWrVuX5htNUmOMJfti+2r9+vVj06ZNU48BAAAANPG5z30uP/7jPz71GEtmZ99PVW0eY6zf2fNdIQQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAABwAHnXu96VRz/60XnEIx6RCy+8cCHnWL2QrwoAAABwkFt3/juW9Ot96cJn7vY5d999d1760pfmPe95T9auXZtTTjklGzZsyPHHH7+ks7hCCAAAAOAA8bGPfSyPeMQj8vCHPzz3v//987znPS9XXnnlkp9HEAIAAAA4QGzdujXHHnvsPffXrl2brVu3Lvl5BCEAAACAZgQhAAAAgAPEMccck5tuuume+1u2bMkxxxyz5OcRhAAAAAAOEKecckquv/763Hjjjfn2t7+dyy+/PBs2bFjy83iXMQAAAIADxOrVq/O6170uT33qU3P33XfnRS96UU444YSlP8+Sf0UAAACAFWBP3iZ+EZ7xjGfkGc94xkLPYckYAAAAQDOCEAAAAEAzghAAAABAM4IQAAAAQDOCEAAAAEAzghAAAABAM4IQAAAAwAHiRS96UX7oh34oj33sYxd6ntUL/eoAAAAAB6sLDl/ir3f7bp/yy7/8yzn33HPzghe8YGnPvQNXCAEAAAAcIH7+538+D37wgxd+HkEIAAAAoBlBCAAAAKAZQQgAAACgGUEIAAAAoBlBCAAAAOAAceaZZ+YJT3hCPv/5z2ft2rW5+OKLF3IebzsPAAAAsDN78DbxS+2yyy5blvO4QggAAACgGUEIAAAAoBlBCAAAAKAZQQgAAABoaYwx9QhLYl++D5tKA7AirDv/HZOd+0sXPnOycwMAsG/WrFmT2267LUceeWSqaupx9tkYI7fddlvWrFmzV58nCAEAAADtrF27Nlu2bMktt9wy9Sj7bc2aNVm7du1efY4gBAAAALRzyCGH5Ljjjpt6jMnYQwgAAACgGUEIAAAAoBlBCAAAAKAZQQgAAACgGUEIAAAAoBlBCAAAAKAZQQgAAACgGUEIAAAAoBlBCAAAAKAZQQgAAACgGUEIAAAAoBlBCAAAAKAZQQgAAACgGUEIAAAAoBlBCAAAAKAZQQgAAACgGUEIAAAAoBlBCAAAAKAZQQgAAACgGUEIAAAAoBlBCAAAAKAZQQgAAACgGUEIAAAAoBlBCAAAAKAZQQgAAACgGUEIAAAAoBlBCAAAAKAZQQgAAACgGUEIAAAAoBlBCAAAAKAZQQgAAACgGUEIAAAAoBlBCAAAAKAZQQgAAACgGUEIAAAAoBlBCAAAAKAZQQgAAACgGUEIAAAAoBlBCAAAAKAZQQgAAACgGUEIAAAAoBlBCAAAAKAZQQgAAACgGUEIAAAAoBlBCAAAAKAZQQgAAACgGUEIAAAAoBlBCAAAAKAZQQgAAACgGUEIAAAAoBlBCAAAAKAZQQgAAACgGUEIAAAAoBlBCAAAAKAZQQgAAACgGUEIAAAAoBlBCAAAAKAZQQgAAACgGUEIAAAAoBlBCAAAAKAZQQgAAACgGUEIAAAAoBlBCAAAAKAZQQgAAACgGUEIAAAAoBlBCAAAAKAZQQgAAACgGUEIAAAAoBlBCAAAAKAZQQgAAACgGUEIAAAAoBlBCAAAAKAZQQgAAACgGUEIAAAAoBlBCAAAAKAZQQgAAACgGUEIAAAAoBlBCAAAAKCZPQ5CVbWqqj5ZVVfP7x9XVR+tqhuq6s1Vdf/58UPn92+YP75uQbMDAAAAsA/25gqh85J8brv7r0zy6jHGI5J8LcmL58dfnORr8+Ovnj8PAAAAgAPEHgWhqlqb5JlJ/nR+v5KcluSt86dckuQ589vPnt/P/PEnz58PAAAAwAFgT68Q+i9J/k2S787vH5nk62OMu+b3tyQ5Zn77mCQ3Jcn88dvnzwcAAADgALDbIFRVz0rylTHG5qU8cVWdU1WbqmrTLbfcspRfGgAAAIBd2JMrhJ6YZENVfSnJ5ZktFXtNkiOqavX8OWuTbJ3f3prk2CSZP354ktt2/KJjjIvGGOvHGOuPPvro/fomAAAAANhzuw1CY4zfHGOsHWOsS/K8JO8bYzw/yfuTnD5/2tlJrpzfvmp+P/PH3zfGGEs6NQAAAAD7bG/eZWxH/zbJy6rqhsz2CLp4fvziJEfOj78syfn7NyIAAAAAS2n17p/yPWOMDyT5wPz2F5P89E6e860kz12C2QAAAABYgP25QggAAACAg5AgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0Mxug1BVramqj1XVp6rqs1X1ivnx46rqo1V1Q1W9uaruPz9+6Pz+DfPH1y34ewAAAABgL+zJFUL/kOS0Mcbjkpyc5GlV9fgkr0zy6jHGI5J8LcmL589/cZKvzY+/ev48AAAAAA4Quw1CY+aO+d1D5h8jyWlJ3jo/fkmS58xvP3t+P/PHn1xVtVQDAwAAALB/9mgPoapaVVXXJPlKkvck+UKSr48x7po/ZUuSY+a3j0lyU5LMH789yZFLODMAAAAA+2GPgtAY4+4xxslJ1ib56SSP2d8TV9U5VbWpqjbdcsst+/vlAAAAANhDe/UuY2OMryd5f5InJDmiqlbPH1qbZOv89tYkxybJ/PHDk9y2k6910Rhj/Rhj/dFHH71v0wMAAACw1/bkXcaOrqoj5rcfkOQpST6XWRg6ff60s5NcOb991fx+5o+/b4wxlnBmAAAAAPbD6t0/JQ9JcklVrcosIF0xxri6qq5LcnlV/Yckn0xy8fz5Fyf571V1Q5KvJnneAuYGAAAAYB/tNgiNMa5N8hM7Of7FzPYT2vH4t5I8d0mmAwAAAGDJ7dUeQgAAAAAc/AQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmdhuEqurYqnp/VV1XVZ+tqvPmxx9cVe+pquvnf/7g/HhV1R9U1Q1VdW1V/eSivwkAAAAA9tyeXCF0V5LfGGMcn+TxSV5aVccnOT/Je8cYj0zy3vn9JHl6kkfOP85J8kdLPjUAAAAA+2y3QWiMcfMY4xPz299I8rkkxyR5dpJL5k+7JMlz5refneSNY+Z/JTmiqh6y1IMDAAAAsG/2ag+hqlqX5CeSfDTJD48xbp4/9LdJfnh++5gkN233aVvmxwAAAAA4AOxxEKqqByZ5W5J/Ncb4u+0fG2OMJGNvTlxV51TVpqradMstt+zNpwIAAACwH/YoCFXVIZnFoEvHGG+fH/7f25aCzf/8yvz41iTHbvfpa+fHvs8Y46Ixxvoxxvqjjz56X+cHAAAAYC/tybuMVZKLk3xujPH72z10VZKz57fPTnLldsdfMH+3sccnuX27pWUAAAAATGz1HjzniUnOSvLpqrpmfuy3klyY5IqqenGSLyc5Y/7YO5M8I8kNSb6Z5IVLOTAAAAAA+2e3QWiM8aEkdR8PP3knzx9JXrqfcwEAAACwIHv1LmMAAAAAHPwEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZlZPPQDAinTB4ROe+/bpzg0AABwUXCEEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQzG6DUFW9vqq+UlWf2e7Yg6vqPVV1/fzPH5wfr6r6g6q6oaquraqfXOTwAAAAAOy9PblC6A1JnrbDsfOTvHeM8cgk753fT5KnJ3nk/OOcJH+0NGMCAAAAsFR2G4TGGP9vkq/ucPjZSS6Z374kyXO2O/7GMfO/khxRVQ9ZolkBAAAAWAL7uofQD48xbp7f/tskPzy/fUySm7Z73pb5MQAAAAAOEPu9qfQYYyQZe/t5VXVOVW2qqk233HLL/o4BAAAAwB7a1yD0v7ctBZv/+ZX58a1Jjt3ueWvnx+5ljHHRGGP9GGP90UcfvY9jAAAAALC3Vu/j512V5OwkF87/vHK74+dW1eVJfibJ7dstLQMAOPhdcPiE5759unMDACvKboNQVV2W5NQkR1XVliQvzywEXVFVL07y5SRnzJ/+ziTPSHJDkm8meeECZgYAAABgP+w2CI0xzryPh568k+eOJC/d36EAAAAAWJz93lQaAAAAgIOLIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANDM6qkHAICD3gWHT3ju26c7NwAABy1XCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0s3rqAQAWZd3575js3F9aM9mpAQAAdssVQgAAAADNCEIAAAAAzQhCAAAAAM0IQgAAAADNCEIAAAAAzQhCAAAAAM0IQgAAAADNCEIAAAAAzQhCAAAAAM0IQgAAAADNCEIAAAAAzQhCAAAAAM0IQgAAAADNCEIAAAAAzQhCAAAAAM0IQgAAAADNCEIAAAAAzQhCAAAAAM0IQgAAAADNCEIAAAAAzQhCAAAAAM0IQgAAAADNCEIAAAAAzQhCAAAAAM0IQgAAAADNCEIAAAAAzQhCAAAAAM0IQgAAAADNCEIAAAAAzQhCAAAAAM0IQgAAAADNCEIAAAAAzQhCAAAAAM0IQgAAAADNCEIAAAAAzQhCAAAAAM0IQgAAAADNCEIAAAAAzQhCAAAAAM0IQgAAAADNCEIAAAAAzQhCAAAAAM0IQgAAAADNCEIAAAAAzQhCAAAAAM0IQgAAAADNCEIAAAAAzQhCAAAAAM0IQgAAAADNCEIAAAAAzQhCAAAAAM0IQgAAAADNCEIAAAAAzQhCAAAAAM0IQgAAAADNCEIAAAAAzQhCAAAAAM0IQgAAAADNCEIAAAAAzQhCAAAAAM0IQgAAAADNCEIAAAAAzQhCAAAAAM0IQgAAAADNCEIAAAAAzQhCAAAAAM0IQgAAAADNrJ56AACAvbXu/HdMdu4vrZns1AAAS8YVQgAAAADNCEIAAAAAzQhCAAAAAM0IQgAAAADNCEIAAAAAzQhCAAAAAM0IQgAAAADNrJ56AGjjgsMnPPft050bAACAA44rhAAAAACaEYQAAAAAmhGEAAAAAJoRhAAAAACaEYQAAAAAmhGEAAAAAJoRhAAAAACaEYQAAAAAmhGEAAAAAJoRhAAAAACaEYQAAAAAmhGEAAAAAJoRhAAAAACaWT31ALCc1p3/jsnO/aU1k50aAAAAvo8rhAAAAACaEYQAAAAAmhGEAAAAAJoRhAAAAACaEYQAAAAAmhGEAAAAAJoRhAAAAACaEYQAAAAAmhGEAAAAAJoRhAAAAACaEYQAAAAAmhGEAAAAAJoRhAAAAACaEYQAAAAAmhGEAAAAAJoRhAAAAACaEYQAAAAAmhGEAAAAAJoRhAAAAACaEYQAAAAAmhGEAAAAAJoRhAAAAACaEYQAAAAAmhGEAAAAAJoRhAAAAACaWT31AAAAANusO/8dk537Sxc+c7JzAyw3VwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANNN+U2mb1gEAAADduEIIAAAAoJn2VwhN6oLDJzz37dOdGwBgL7iiGwCWniuEAAAAAJpxhRAAANwXV3TDyuXne9m54vPAspArhKrqaVX1+aq6oarOX8Q5AAAAANg3Sx6EqmpVkj9M8vQkxyc5s6qOX+rzAAAAALBvFrFk7KeT3DDG+GKSVNXlSZ6d5LoFnAsAAABg1ywRvJdFLBk7JslN293fMj8GAAAAwAGgxhhL+wWrTk/ytDHGP5/fPyvJz4wxzt3heeckOWd+99FJPr+kgxwcjkpy69RDsGy83r14vXvxevfi9e7F692L17sXr3cvXV/vh40xjt7ZA4tYMrY1ybHb3V87P/Z9xhgXJbloAec/aFTVpjHG+qnnYHl4vXvxevfi9e7F692L17sXr3cvXu9evN73toglYx9P8siqOq6q7p/keUmuWsB5AAAAANgHS36F0Bjjrqo6N8m7k6xK8voxxmeX+jwAAAAA7JtFLBnLGOOdSd65iK+9wrReMteQ17sXr3cvXu9evN69eL178Xr34vXuxeu9gyXfVBoAAACAA9si9hACAAAA4AAmCAEAAAA0IwgBAAAANCMILaOqWlVV7596DpZHzRw79Rwsn6r6sao6dH771Kr69ao6YuKxWID5f88vnXoOlo+f7z7mP9+vmnoOlk9VrZp6Bhavqn506hngQCMILaMxxt1JvltVh089C4s3Zju2e7e9Xt6W5O6qekRm72JwbJI3TTsSizD/7/nDqur+U8/CsvHz3cT85/tJU8/Bsrqxqi6qqidXVU09DAvzP7bdqKq3TTgHy6CqrtrVx9TzHSgW8rbz7NIdST5dVe9Jcue2g2OMX59uJBboE1V1yhjj41MPwrL47hjjrqr6pSSvHWO8tqo+OfVQLMwXk3x4/o+K7f97/vvTjcQC+fnu5ZPzn+235Pt/vt8+3Ugs0GOSPCvJS5NcXFVXJ7l8jPGhacdiiW0f+x4+2RQslyckuSnJZUk+mu9//ZkThJbf2+cf9PAzSZ5fVV/O7B+UldnFQydNOxYL8p2qOjPJ2Ul+cX7skAnnYbG+MP+4X5IHTTwLi+fnu5c1SW5Lctp2x0b8G25FGmN8M8kVSa6oqh9M8pokH0xiKdnKMu7jNivTjyR5SpIzk/zTJO9IctkY47OTTnWAqdmqFpZTVR2dJGOMW6aehcWqqoft7PgY48vLPQuLV1XHJ/nVJB8ZY1xWVcclOWOM8cqJRwP2k59vWNmq6v9IsjHJ05JsSvLmMYZlRStIVd2d7/2C9gFJvrntocx+YXvYVLOxWPM9AM9M8p+SvGKM8bqJRzpgCELLZL4e+eVJzs3st8mV5K7MLjv/nSlnY7Gq6klJHjnG+LN5DHzgGOPGqedisea/YTx2jHHt1LOwtHa37nyMsWG5ZmF5zDecfeMY4/lTz8LyqKpHJfmjJD88xnhsVZ2UZMMY4z9MPBoLUFVfSvLJzK4SumqMceeuPwM4GMxD0DMzi0HrklyV5PVjjK1TznUgEYSWSVW9LMnTk5yzLQZU1cMz+8fGu8YYr55yPhajql6eZH2SR48xHlVVD03yljHGEycejQWoqg8k2ZDZctzNSb6S5MNjjJdNORdLq6puyS7WpI8xPjjFXCxWVX0oyWljjG9PPQuLV1UfTPJ/JflvY4yfmB/7zBjjsdNOxiJU1WFjjL+beg6WR1X95yQXjzGum3oWFqeq3pjksZm9yc/lY4zPTDzSAUkQWibzjSefMsa4dYfjRyf5n9v+scHKUlXXJPmJJJ/Y7h+U19pDaGWqqk+OMX6iqv55ZlcHvdzrvfLMrxbZtib9pFiT3sL8H5Y/ntlvF20ivsJV1cfHGKds++/6/Ng1Y4yTJx6NBXBFWC/zf6e9MLNf4P1ZZn+H3z7tVCy1qvputvv7Ot/bN8oSwe142/nlc8iOMSi5Zx8hm1KuXN+ev/38SJKq+oGJ52GxVlfVQ5KckeTqqYdhMcYYd48x3jXGODvJ45PckOQDVXXuxKOxWF/I7Od62ybi2z5YmW6tqh/L9/7+Pj3JzdOOxAL9SZLfTPKdJJkv937epBOxMGOMP51frf+CzJYRXVtVb6qqfzztZCylMcb9xhgP2u7jsPnHg8Sg7/EuY8tnV5eYu/x85bqiqv5bkiOq6l8keVGSP514Jhbnd5K8O8mHxhgfny8LvX7imViAnaxJ/4Mk/8+UM7FYY4xXTD0Dy+qlSS5K8piq2prkxiT/bNqRWKB/NMb42GzLz3vcNdUwLN78at/HzD9uTfKpJC+rql8ZY4iBK4glgrtmydgy2W5X+3s9lGTNGMNVQitUVT0lyS9k9lq/e4zxnolHAvaDNem92ES8t/mVvfcbY3xj6llYnKr6y8ze+OUtY4yfnF8R9uIxxtMnHo0FqKpXJ3lWkvdlFgo+tt1jnx9jPHqy4VhylgjumiAEC1RVrxxj/NvdHePgVlX/Zozxe1X12nxvffI9xhi/PsFYLMgOa9K3f72tSV+BbCLeU1X9xyS/N8b4+vz+Dyb5jTHGb086GAsxv6L3oiQ/m+RrmV0R9vwxxpcnHYyFqKoXJrliZ+8mV1WHiwUrU1U9OrMwdGaSDyf5kzHG+6edanqCECxQVX1ijPGTOxyzyfAKU1W/OMb4i6o6e2ePjzEuWe6ZgKVhE/Gett9Mertj9/o7nZVl2xVhSb6Z5HljjEsnHokFqKr3jjGevLtjrBzzv8uflVkQOjbJFUmelOTO7ksE7SEEC1BVv5bkJUkeXlXXbvfQgzIr0qwgY4y/mP8p/MAKM8a4O8m7krxrvnfUmZltIv6KMcbrpp2OBVpVVYeOMf4hSarqAUkOnXgmllhVHZbZflHHJLkyyV/N7/9GkmuTCEIrSFWtSfKPkhw1v+pv2xWfh2X2vwFWoB2WCP7H7ZYIvrKqPj/dZAcGQQgW401J/jLJ/53k/O2Of2OM8dVpRmJR7DECK5tNxFu6NMl7q+rP5vdfmET0X3n+e2ZLxD6S5F8k+XeZRYJfGmNcM+FcLMavJPlXSR6a5BPbHf+7JAL/ynVtkt/e2RLBJD+93MMcaCwZgwWav2XtljHGP1TVqZktN3jjtj0JWBnsMQIrl03E+6qqpyfZtoTkPWOMd085D0uvqj49xjhxfntVkpuT/OgY41vTTsYiVdW/HGO8duo5WB6WCO6aIAQLVFXXJFmf2W+U35nZ5cgnjDGeMeFYLDF7jMDKZRNxWLl23BfKPlErW1X9k109PsZ4+3LNwuJtt0Tw/UlOzfcvEXzXGOMxE412QLFkDBbru2OMu+Z/Ab12jPHaqvrk1EOxtOwxAivXGON+U8/A8qmqD40xnlRV34gA2MHjqurv5rcryQPm973eK9Mv7uKxkUQQWlksEdwDrhCCBaqqjyb5L5mtSf/FMcaNVfWZMcZjp52MpbaTPUauSvL6McbWKecCYM9V1cPHGF+ceg4AloYlgrsmCMECVdXxSX41yUfGGJdV1XFJzhhjvHLi0VhC9hgBWBmqavMY46fsLwErW1U9M8kJSdZsOzbG+J3pJmKpWSK4ZwQhgP1kjxGAlWG+rPstSV6S5Pd3fHyMca9jwMGlqv44s71l/nGSP01yepKPjTFePOlgLKnt3iVyZ8YY40XLNswBzB5CsEBV9cjM3nr++Hz/byAePtlQLDl7jACsGM9L8pwkq5I8aNpRgAX52THGSVV17RjjFVX1n5P85dRDsbTGGC+ceoaDgSAEi/VnSV6e5NWZ/RbihUnEAwA4MD1tjPHKqjrU8hFYsf5+/uc3q+qhSW5L8pAJ52HBLBG8b/6PKSzWA8YY781seeaXxxgXZLbxMABw4Nn2G+XnTDkEsFBXV9URSf5TZu8+9aUkb5pyIBZnvkRwY5J/mdl2Ds9N8rBJhzqA2EMIFqiq/jrJk5K8Ncn7kmxNcuEY49GTDgYA3EtVXZZkfWZvU/yF7R/KbM+JkyYZDFiI+bvErhlj3D71LCzGfGngSdv9+cAkfznG+LmpZzsQWDIGi3VeZpvW/XqSf5/ktCRnTzoRALBTY4wzq+pHknwgyT/NLAR9J99bYgIc5Kpqc5LXJ3nTGONrSf5h4pFYLEsEd0EQggUaY3x8fvOOfO8ydADgAFRVq5O8LMlRSS7JLAgdm9megP9uwtGApbMxs3+Xf7yqNmX28/0/h6UzK9WOSwRHkj+ZdKIDiCVjsABVddWuHh9jbFiuWQCAPVNVr87s3cX+9RjjG/NjhyV5VZK/H2OcN+V8wNKpqvsleVaSP0pyd2Zh6DVjjK9OOhgLY4ngvQlCsABVdUuSm5JcluSjmf2G8R5jjA9OMRcAcN+q6vokj9rxSoGqWpXkb8YYj5xmMmApVdVJmV0l9Iwk705yaWb7fp41xjh5wtFYYjtZIsh2LBmDxfiRJE9JcmZmexC8I8llY4zPTjoVALArY2fLRsYYd1eV36LCCjAPBF9PcnGS88cY2/YQ+mhVPXGywVgUSwR3wRVCsGDzSxPPzGzd6ivGGK+beCQAYCeq6n8kefsY4407HP9nSc6w5BsOflX18DHGF6eeg+VlieDOCUKwIPMQ9MzMYtC6JFclef0YY+uUcwEAO1dVxyR5e2bvSrN5fnh9kgck+SV/h8PBr6oOT/LyJD8/P/TBJL9jX5mVyxLB+yYIwQJU1RuTPDbJO5NcPsb4zMQjAQB7qKpOS3LC/O51Y4z3TjkPsHSq6m1JPpPZOwkmyVlJHjfG+CfTTcWi7LBE8G3bLRFMVb29++suCMECVNV3k9w5v7v9D1lltj/BYcs/FQAA9FZV1+x4VcjOjrEyWCK4azaVhgUYY9xv6hkAAIB7+fuqetIY40NJMt9I+u8nnonFua2qfj+WCO6UK4QAAABooapOzmy52OGZXb3/1SRnjzGunXIuFsMSwV0ThAAAAGilqrZt4XBnkueNMS6dch4WwxLBXbOsBQAAgBWtqg6rqt+sqtdV1VOSfCPJC5LckOSMaadjgf6+qp607Y4lgt/PFUIAAACsaFV1ZZKvJflIkicn+aHMloydN8a4ZsLRWCBLBHdNEAIAAGBFq6pPjzFOnN9eleTmJD86xvjWtJOxHCwR3DlLxgAAAFjpvrPtxhjj7iRbxKCVyxLBPeMKIQAAAFa0qro7s6tDktnSoQck+eb89hhjHHZfn8vBxxLBPSMIAQAAACuGJYJ7xpIxAAAAYCWxRHAPuEIIAAAAWDEsEdwzghAAAABAM5aMAQAAADQjCAEAAAA0IwgBAO1V1ZFVdc3842+rauv89h1V9V/nzzm1qn52u8+5oKr+z+mmBgDYd6unHgAAYGpjjNuSnJzMQk+SO8YYr9rhaacmuSPJXy/nbAAAi+AKIQCA+zC/KujqqlqX5FeT/Ov5lUM/t8Pzfqyq3lVVm6vq/6uqx0wyMADAHnKFEADAbowxvlRVf5ztrhyqqidv95SLkvzqGOP6qvqZJP81yWkTjAoAsEcEIQCA/VBVD0zys0neUlXbDh863UQAALsnCAEA7J/7Jfn6GOPkqQcBANhT9hACANgz30jyoB0PjjH+LsmNVfXcJKmZxy33cAAAe0MQAgDYM3+R5Jd2tql0kucneXFVfSrJZ5M8e9mnAwDYCzXGmHoGAAAAAJaRK4QAAAAAmhGEAAAAAJoRhAAAAACaEYQAAAAAmhGEAAAAAJoRhAAAAACaEYQAAAAAmhGEAAAAAJr5/wEjypXVChIbgQAAAABJRU5ErkJggg==", "text/plain": [ "
" - ], - "image/png": "iVBORw0KGgoAAAANSUhEUgAABIQAAASQCAYAAACNlZS7AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAAsTAAALEwEAmpwYAAA6G0lEQVR4nO3df7TldX3f+9fbGWRsFIhAEmWIg/FXQJAkQ6LR5FJcxp8ZTRcyUotEbUmiNLTm3pakWUtMm15MbazRNCkJRmwQxB+3EDRa4697NVadUUTFuEDRxcwiFVCJYIyCn/vH3oPjMMzPs8935rwfj7XOmr2/e5/zfZ+1Pcz4PN/PZ9cYIwAAAAD0cb+pBwAAAABgeQlCAAAAAM0IQgAAAADNCEIAAAAAzQhCAAAAAM0IQgAAAADNrJ56gCQ56qijxrp166YeAwAAAGDF2Lx5861jjKN39tgBEYTWrVuXTZs2TT0GAAAAwIpRVV++r8csGQMAAABoRhACAAAAaEYQAgAAAGjmgNhDCAAAAGA5fec738mWLVvyrW99a+pR9tuaNWuydu3aHHLIIXv8OYIQAAAA0M6WLVvyoAc9KOvWrUtVTT3OPhtj5LbbbsuWLVty3HHH7fHnWTIGAAAAtPOtb30rRx555EEdg5KkqnLkkUfu9ZVOghAAAADQ0sEeg7bZl+9DEAIAAACY+93f/d2ccMIJOemkk3LyySfnox/96H5/zauuuioXXnjhEkyXPPCBD1ySr2MPIQAAAIAkH/nIR3L11VfnE5/4RA499NDceuut+fa3v71Hn3vXXXdl9eqdZ5YNGzZkw4YNSznqfnOFEAAAAECSm2++OUcddVQOPfTQJMlRRx2Vhz70oVm3bl1uvfXWJMmmTZty6qmnJkkuuOCCnHXWWXniE5+Ys846K49//OPz2c9+9p6vd+qpp2bTpk15wxvekHPPPTe33357Hvawh+W73/1ukuTOO+/Msccem+985zv5whe+kKc97Wn5qZ/6qfzcz/1c/uZv/iZJcuONN+YJT3hCTjzxxPz2b//2kn2vghAAAABAkl/4hV/ITTfdlEc96lF5yUtekg9+8IO7/Zzrrrsuf/VXf5XLLrssGzduzBVXXJFkFpduvvnmrF+//p7nHn744Tn55JPv+bpXX311nvrUp+aQQw7JOeeck9e+9rXZvHlzXvWqV+UlL3lJkuS8887Lr/3ar+XTn/50HvKQhyzZ9yoIAQAAAGS2P8/mzZtz0UUX5eijj87GjRvzhje8YZefs2HDhjzgAQ9Ikpxxxhl561vfmiS54oorcvrpp9/r+Rs3bsyb3/zmJMnll1+ejRs35o477shf//Vf57nPfW5OPvnk/Mqv/EpuvvnmJMmHP/zhnHnmmUmSs846a6m+VXsIAQAAAGyzatWqnHrqqTn11FNz4okn5pJLLsnq1avvWea149u7/8AP/MA9t4855pgceeSRufbaa/PmN785f/zHf3yvr79hw4b81m/9Vr761a9m8+bNOe2003LnnXfmiCOOyDXXXLPTmRbxbmiuEAIAAABI8vnPfz7XX3/9PfevueaaPOxhD8u6deuyefPmJMnb3va2XX6NjRs35vd+7/dy++2356STTrrX4w984ANzyimn5LzzzsuznvWsrFq1KocddliOO+64vOUtb0mSjDHyqU99KknyxCc+MZdffnmS5NJLL12S7zMRhAAAAACSJHfccUfOPvvsHH/88TnppJNy3XXX5YILLsjLX/7ynHfeeVm/fn1WrVq1y69x+umn5/LLL88ZZ5xxn8/ZuHFj/vzP/zwbN26859ill16aiy++OI973ONywgkn5Morr0ySvOY1r8kf/uEf5sQTT8zWrVuX5htNUmOMJfti+2r9+vVj06ZNU48BAAAANPG5z30uP/7jPz71GEtmZ99PVW0eY6zf2fNdIQQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAABwAHnXu96VRz/60XnEIx6RCy+8cCHnWL2QrwoAAABwkFt3/juW9Ot96cJn7vY5d999d1760pfmPe95T9auXZtTTjklGzZsyPHHH7+ks7hCCAAAAOAA8bGPfSyPeMQj8vCHPzz3v//987znPS9XXnnlkp9HEAIAAAA4QGzdujXHHnvsPffXrl2brVu3Lvl5BCEAAACAZgQhAAAAgAPEMccck5tuuume+1u2bMkxxxyz5OcRhAAAAAAOEKecckquv/763Hjjjfn2t7+dyy+/PBs2bFjy83iXMQAAAIADxOrVq/O6170uT33qU3P33XfnRS96UU444YSlP8+Sf0UAAACAFWBP3iZ+EZ7xjGfkGc94xkLPYckYAAAAQDOCEAAAAEAzghAAAABAM4IQAAAA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AADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0IwgBAAAANCMIAQAAADQjCAEAAAA0Mxug1BVramqj1XVp6rqs1X1ivnx46rqo1V1Q1W9uaruPz9+6Pz+DfPH1y34ewAAAABgL+zJFUL/kOS0Mcbjkpyc5GlV9fgkr0zy6jHGI5J8LcmL589/cZKvzY+/ev48AAAAAA4Quw1CY+aO+d1D5h8jyWlJ3jo/fkmS58xvP3t+P/PHn1xVtVQDAwAAALB/9mgPoapaVVXXJPlKkvck+UKSr48x7po/ZUuSY+a3j0lyU5LMH789yZFLODMAAAAA+2GPgtAY4+4xxslJ1ib56SSP2d8TV9U5VbWpqjbdcsst+/vlAAAAANhDe/UuY2OMryd5f5InJDmiqlbPH1qbZOv89tYkxybJ/PHDk9y2k6910Rhj/Rhj/dFHH71v0wMAAACw1/bkXcaOrqoj5rcfkOQpST6XWRg6ff60s5NcOb991fx+5o+/b4wxlnBmAAAAAPbD6t0/JQ9JcklVrcosIF0xxri6qq5LcnlV/Yckn0xy8fz5Fyf571V1Q5KvJnneAuYGAAAAYB/tNgiNMa5N8hM7Of7FzPYT2vH4t5I8d0mmAwAAAGDJ7dUeQgAAAAAc/AQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmdhuEqurYqnp/VV1XVZ+tqvPmxx9cVe+pquvnf/7g/HhV1R9U1Q1VdW1V/eSivwkAAAAA9tyeXCF0V5LfGGMcn+TxSV5aVccnOT/Je8cYj0zy3vn9JHl6kkfOP85J8kdLPjUAAAAA+2y3QWiMcfMY4xPz299I8rkkxyR5dpJL5k+7JMlz5refneSNY+Z/JTmiqh6y1IMDAAAAsG/2ag+hqlqX5CeSfDTJD48xbp4/9LdJfnh++5gkN233aVvmxwAAAAA4AOxxEKqqByZ5W5J/Ncb4u+0fG2OMJGNvTlxV51TVpqradMstt+zNpwIAAACwH/YoCFXVIZnFoEvHGG+fH/7f25aCzf/8yvz41iTHbvfpa+fHvs8Y46Ixxvoxxvqjjz56X+cHAAAAYC/tybuMVZKLk3xujPH72z10VZKz57fPTnLldsdfMH+3sccnuX27pWUAAAAATGz1HjzniUnOSvLpqrpmfuy3klyY5IqqenGSLyc5Y/7YO5M8I8kNSb6Z5IVLOTAAAAAA+2e3QWiM8aEkdR8PP3knzx9JXrqfcwEAAACwIHv1LmMAAAAAHPwEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYEIQAAAIBmBCEAAACAZgQhAAAAgGYE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\n" + ] }, "metadata": { "needs_background": "light" - } + }, + "output_type": "display_data" } + ], + "source": [ + "surv_title = df.pivot_table(values='PassengerId', index='Title',\n", + " columns='Survived', aggfunc='count')\n", + "\n", + "surv_title.plot(kind='bar', figsize=(20,20))" ] }, { "cell_type": "code", - "source": [ - "train = pd.read_csv('/content/train.csv')\n", - "train.head()" - ], + "execution_count": 41, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -1387,33 +1368,9 @@ "id": "DRfmokz-iUbB", "outputId": "b62ef0e7-bc6f-45fb-8b8c-7351abb040b0" }, - "execution_count": 41, "outputs": [ { - "output_type": "execute_result", "data": { - "text/plain": [ - " PassengerId Survived Pclass \\\n", - "0 1 0 3 \n", - "1 2 1 1 \n", - "2 3 1 3 \n", - "3 4 1 1 \n", - "4 5 0 3 \n", - "\n", - " Name Sex Age SibSp \\\n", - "0 Braund, Mr. Owen Harris male 22.0 1 \n", - "1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 \n", - "2 Heikkinen, Miss. Laina female 26.0 0 \n", - "3 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 \n", - "4 Allen, Mr. William Henry male 35.0 0 \n", - "\n", - " Parch Ticket Fare Cabin Embarked \n", - "0 0 A/5 21171 7.2500 NaN S \n", - "1 0 PC 17599 71.2833 C85 C \n", - "2 0 STON/O2. 3101282 7.9250 NaN S \n", - "3 0 113803 53.1000 C123 S \n", - "4 0 373450 8.0500 NaN S " - ], "text/html": [ "\n", "
\n", @@ -1605,30 +1562,54 @@ "
\n", " \n", " " + ], + "text/plain": [ + " PassengerId Survived Pclass \\\n", + "0 1 0 3 \n", + "1 2 1 1 \n", + "2 3 1 3 \n", + "3 4 1 1 \n", + "4 5 0 3 \n", + "\n", + " Name Sex Age SibSp \\\n", + "0 Braund, Mr. Owen Harris male 22.0 1 \n", + "1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 \n", + "2 Heikkinen, Miss. Laina female 26.0 0 \n", + "3 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 \n", + "4 Allen, Mr. William Henry male 35.0 0 \n", + "\n", + " Parch Ticket Fare Cabin Embarked \n", + "0 0 A/5 21171 7.2500 NaN S \n", + "1 0 PC 17599 71.2833 C85 C \n", + "2 0 STON/O2. 3101282 7.9250 NaN S \n", + "3 0 113803 53.1000 C123 S \n", + "4 0 373450 8.0500 NaN S " ] }, + "execution_count": 41, "metadata": {}, - "execution_count": 41 + "output_type": "execute_result" } + ], + "source": [ + "train = pd.read_csv('/content/train.csv')\n", + "train.head()" ] }, { "cell_type": "code", - "source": [ - "train = train.drop(['PassengerId','Name','Ticket','Cabin'], axis=1)" - ], + "execution_count": 24, "metadata": { "id": "q7limL6mhydv" }, - "execution_count": 24, - "outputs": [] + "outputs": [], + "source": [ + "train = train.drop(['PassengerId','Name','Ticket','Cabin'], axis=1)" + ] }, { "cell_type": "code", - "source": [ - "test = pd.read_csv('/content/test.csv')\n", - "test.head()" - ], + "execution_count": 43, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -1637,26 +1618,9 @@ "id": "JIxbbbJYiiVh", "outputId": "37ebc92d-841e-4bb6-b170-b8aaad1355d3" }, - "execution_count": 43, "outputs": [ { - "output_type": "execute_result", "data": { - "text/plain": [ - " PassengerId Pclass Name Sex \\\n", - "0 892 3 Kelly, Mr. James male \n", - "1 893 3 Wilkes, Mrs. James (Ellen Needs) female \n", - "2 894 2 Myles, Mr. Thomas Francis male \n", - "3 895 3 Wirz, Mr. Albert male \n", - "4 896 3 Hirvonen, Mrs. Alexander (Helga E Lindqvist) female \n", - "\n", - " Age SibSp Parch Ticket Fare Cabin Embarked \n", - "0 34.5 0 0 330911 7.8292 NaN Q \n", - "1 47.0 1 0 363272 7.0000 NaN S \n", - "2 62.0 0 0 240276 9.6875 NaN Q \n", - "3 27.0 0 0 315154 8.6625 NaN S \n", - "4 22.0 1 1 3101298 12.2875 NaN S " - ], "text/html": [ "\n", "
\n", @@ -1842,30 +1806,48 @@ "
\n", " \n", " " + ], + "text/plain": [ + " PassengerId Pclass Name Sex \\\n", + "0 892 3 Kelly, Mr. James male \n", + "1 893 3 Wilkes, Mrs. James (Ellen Needs) female \n", + "2 894 2 Myles, Mr. Thomas Francis male \n", + "3 895 3 Wirz, Mr. Albert male \n", + "4 896 3 Hirvonen, Mrs. Alexander (Helga E Lindqvist) female \n", + "\n", + " Age SibSp Parch Ticket Fare Cabin Embarked \n", + "0 34.5 0 0 330911 7.8292 NaN Q \n", + "1 47.0 1 0 363272 7.0000 NaN S \n", + "2 62.0 0 0 240276 9.6875 NaN Q \n", + "3 27.0 0 0 315154 8.6625 NaN S \n", + "4 22.0 1 1 3101298 12.2875 NaN S " ] }, + "execution_count": 43, "metadata": {}, - "execution_count": 43 + "output_type": "execute_result" } + ], + "source": [ + "test = pd.read_csv('/content/test.csv')\n", + "test.head()" ] }, { "cell_type": "code", - "source": [ - "result = pd.DataFrame(test['PassengerId'])\n", - "test = test.drop(['PassengerId', 'Name','Ticket','Cabin'], axis=1)" - ], + "execution_count": 27, "metadata": { "id": "h_O9LZDEidoB" }, - "execution_count": 27, - "outputs": [] + "outputs": [], + "source": [ + "result = pd.DataFrame(test['PassengerId'])\n", + "test = test.drop(['PassengerId', 'Name','Ticket','Cabin'], axis=1)" + ] }, { "cell_type": "code", - "source": [ - "pip install scikit-learn" - ], + "execution_count": 28, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -1873,11 +1855,10 @@ "id": "oOgSyzBOipGL", "outputId": "c70b0273-7414-465c-a9d8-8a838c666469" }, - "execution_count": 28, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/\n", "Requirement already satisfied: scikit-learn in /usr/local/lib/python3.9/dist-packages (1.2.2)\n", @@ -1887,25 +1868,26 @@ "Requirement already satisfied: joblib>=1.1.1 in /usr/local/lib/python3.9/dist-packages (from scikit-learn) (1.1.1)\n" ] } + ], + "source": [ + "pip install scikit-learn" ] }, { "cell_type": "code", - "source": [ - "from sklearn.preprocessing import LabelEncoder\n", - "le = LabelEncoder()" - ], + "execution_count": 29, "metadata": { "id": "3CgOw3D1ixwV" }, - "execution_count": 29, - "outputs": [] + "outputs": [], + "source": [ + "from sklearn.preprocessing import LabelEncoder\n", + "le = LabelEncoder()" + ] }, { "cell_type": "code", - "source": [ - "test.info()" - ], + "execution_count": 30, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -1913,11 +1895,10 @@ "id": "2_vFCLQ-i1cB", "outputId": "bb70f333-1a43-4883-c642-5781b0295390" }, - "execution_count": 30, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "\n", "RangeIndex: 418 entries, 0 to 417\n", @@ -1935,30 +1916,29 @@ "memory usage: 23.0+ KB\n" ] } + ], + "source": [ + "test.info()" ] }, { "cell_type": "code", + "execution_count": 31, + "metadata": { + "id": "-XRdWbVUs17d" + }, + "outputs": [], "source": [ "def transf_data(df):\n", " embarked = df.groupby('Embarked').count()['PassengerId']\n", " embarked_max = embarked[embarked == embarked.max()].index[0]\n", " df.loc[df['Embarked'].isnull(), 'Embarked'] = embarked_max\n", " return df" - ], - "metadata": { - "id": "-XRdWbVUs17d" - }, - "execution_count": 31, - "outputs": [] + ] }, { "cell_type": "code", - "source": [ - "le.fit(train['Sex'])\n", - "print(le.classes_)\n", - "train['Sex'] = le.transform(train['Sex'])" - ], + "execution_count": 45, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -1966,36 +1946,38 @@ "id": "uMOvsOjltT5W", "outputId": "41995cb6-ae58-4404-89c2-f4b26d59856d" }, - "execution_count": 45, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "['female' 'male']\n" ] } + ], + "source": [ + "le.fit(train['Sex'])\n", + "print(le.classes_)\n", + "train['Sex'] = le.transform(train['Sex'])" ] }, { "cell_type": "code", + "execution_count": 49, + "metadata": { + "id": "zLoliPTyu4Xg" + }, + "outputs": [], "source": [ "classes = {}\n", "le.fit(train['Sex'])\n", "classes['Sex'] = le.classes_\n", "train['Sex'] = le.transform(train['Sex'])" - ], - "metadata": { - "id": "zLoliPTyu4Xg" - }, - "execution_count": 49, - "outputs": [] + ] }, { "cell_type": "code", - "source": [ - "print(classes)" - ], + "execution_count": 51, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -2003,23 +1985,22 @@ "id": "UBIqNdjdvBZi", "outputId": "4ec94178-046a-46f2-8d0c-a1c469fbf00d" }, - "execution_count": 51, "outputs": [ { - "output_type": "stream", "name": "stdout", + "output_type": "stream", "text": [ "{'Sex': array([0, 1])}\n" ] } + ], + "source": [ + "print(classes)" ] }, { "cell_type": "code", - "source": [ - "le.fit(classes['Embarked'])\n", - "test['Embarked'] = le.transform(test['Embarked'])" - ], + "execution_count": 67, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -2028,12 +2009,11 @@ "id": "wJ6O64GG0kWo", "outputId": "ebf34ce6-04f7-428f-cf09-11961c7e8aa7" }, - "execution_count": 67, "outputs": [ { - "output_type": "error", "ename": "KeyError", "evalue": "ignored", + "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)", @@ -2041,44 +2021,46 @@ "\u001b[0;31mKeyError\u001b[0m: 'Embarked'" ] } + ], + "source": [ + "le.fit(classes['Embarked'])\n", + "test['Embarked'] = le.transform(test['Embarked'])" ] }, { "cell_type": "code", - "source": [], + "execution_count": null, "metadata": { "id": "ufM5xz5w0kqn" }, - "execution_count": null, - "outputs": [] + "outputs": [], + "source": [] }, { "cell_type": "code", + "execution_count": 52, + "metadata": { + "id": "ivP7_uvZvMgE" + }, + "outputs": [], "source": [ "from sklearn.model_selection import cross_val_score\n", "from sklearn.neighbors import KNeighborsClassifier\n", "from sklearn.ensemble import RandomForestClassifier" - ], - "metadata": { - "id": "ivP7_uvZvMgE" - }, - "execution_count": 52, - "outputs": [] + ] }, { "cell_type": "code", - "source": [], + "execution_count": 64, "metadata": { "id": "LTbd9v1dzJ0x" }, - "execution_count": 64, - "outputs": [] + "outputs": [], + "source": [] }, { "cell_type": "code", - "source": [ - "model_rf" - ], + "execution_count": 55, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -2087,28 +2069,28 @@ "id": "kVzi7lp0zO4j", "outputId": "a970abf1-da0a-4e97-aedb-c3e9d27a5958" }, - "execution_count": 55, "outputs": [ { - "output_type": "execute_result", "data": { - "text/plain": [ - "RandomForestClassifier()" - ], "text/html": [ "
RandomForestClassifier()
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" + ], + "text/plain": [ + "RandomForestClassifier()" ] }, + "execution_count": 55, "metadata": {}, - "execution_count": 55 + "output_type": "execute_result" } + ], + "source": [ + "model_rf" ] }, { "cell_type": "code", - "source": [ - "model_kn" - ], + "execution_count": 62, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -2117,32 +2099,28 @@ "id": "q3D4okcAzjJu", "outputId": "4381e081-81b1-4673-ec3a-4f30e1f353be" }, - "execution_count": 62, "outputs": [ { - "output_type": "execute_result", "data": { - "text/plain": [ - "KNeighborsClassifier(n_neighbors=20)" - ], "text/html": [ "
KNeighborsClassifier(n_neighbors=20)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" + ], + "text/plain": [ + "KNeighborsClassifier(n_neighbors=20)" ] }, + "execution_count": 62, "metadata": {}, - "execution_count": 62 + "output_type": "execute_result" } + ], + "source": [ + "model_kn" ] }, { "cell_type": "code", - "source": [ - "# ValueError: could not convert string to float: 'Braund, Mr. Owen Harris'\n", - "\n", - "scores = cross_val_score(model_rf, train, target, cv=5)\n", - "print(scores)\n", - "# print(scores.mean())" - ], + "execution_count": 68, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -2151,12 +2129,11 @@ "id": "hL1xYCs30U-O", "outputId": "33f3897a-f99c-468d-cf09-20f1272da458" }, - "execution_count": 68, "outputs": [ { - "output_type": "error", "ename": "ValueError", "evalue": "ignored", + "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", @@ -2167,7 +2144,31 @@ "\u001b[0;31mValueError\u001b[0m: \nAll the 5 fits failed.\nIt is very likely that your model is misconfigured.\nYou can try to debug the error by setting error_score='raise'.\n\nBelow are more details about the failures:\n--------------------------------------------------------------------------------\n1 fits failed with the following error:\nTraceback (most recent call last):\n File \"/usr/local/lib/python3.9/dist-packages/sklearn/model_selection/_validation.py\", line 686, in _fit_and_score\n estimator.fit(X_train, y_train, **fit_params)\n File \"/usr/local/lib/python3.9/dist-packages/sklearn/ensemble/_forest.py\", line 345, in fit\n X, y = self._validate_data(\n File \"/usr/local/lib/python3.9/dist-packages/sklearn/base.py\", line 584, in _validate_data\n X, y = check_X_y(X, y, **check_params)\n File \"/usr/local/lib/python3.9/dist-packages/sklearn/utils/validation.py\", line 1106, in check_X_y\n X = check_array(\n File \"/usr/local/lib/python3.9/dist-packages/sklearn/utils/validation.py\", line 879, in check_array\n array = _asarray_with_order(array, order=order, dtype=dtype, xp=xp)\n File \"/usr/local/lib/python3.9/dist-packages/sklearn/utils/_array_api.py\", line 185, in _asarray_with_order\n array = numpy.asarray(array, order=order, dtype=dtype)\n File \"/usr/local/lib/python3.9/dist-packages/pandas/core/generic.py\", line 2064, in __array__\n return np.asarray(self._values, dtype=dtype)\nValueError: could not convert string to float: 'Baumann, Mr. John D'\n\n--------------------------------------------------------------------------------\n4 fits failed with the following error:\nTraceback (most recent call last):\n File \"/usr/local/lib/python3.9/dist-packages/sklearn/model_selection/_validation.py\", line 686, in _fit_and_score\n estimator.fit(X_train, y_train, **fit_params)\n File \"/usr/local/lib/python3.9/dist-packages/sklearn/ensemble/_forest.py\", line 345, in fit\n X, y = self._validate_data(\n File \"/usr/local/lib/python3.9/dist-packages/sklearn/base.py\", line 584, in _validate_data\n X, y = check_X_y(X, y, **check_params)\n File \"/usr/local/lib/python3.9/dist-packages/sklearn/utils/validation.py\", line 1106, in check_X_y\n X = check_array(\n File \"/usr/local/lib/python3.9/dist-packages/sklearn/utils/validation.py\", line 879, in check_array\n array = _asarray_with_order(array, order=order, dtype=dtype, xp=xp)\n File \"/usr/local/lib/python3.9/dist-packages/sklearn/utils/_array_api.py\", line 185, in _asarray_with_order\n array = numpy.asarray(array, order=order, dtype=dtype)\n File \"/usr/local/lib/python3.9/dist-packages/pandas/core/generic.py\", line 2064, in __array__\n return np.asarray(self._values, dtype=dtype)\nValueError: could not convert string to float: 'Braund, Mr. Owen Harris'\n" ] } + ], + "source": [ + "# ValueError: could not convert string to float: 'Braund, Mr. Owen Harris'\n", + "#!!! Ошибка в данных курса!!!\n", + "\n", + "scores = cross_val_score(model_rf, train, target, cv=5)\n", + "print(scores)\n", + "# print(scores.mean())" ] } - ] -} \ No newline at end of file + ], + "metadata": { + "colab": { + "authorship_tag": "ABX9TyNEVyRNtSXPLUtt/75byQlH", + "include_colab_link": true, + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/README.md b/README.md index 8b8a228..d260e37 100644 --- a/README.md +++ b/README.md @@ -3,4 +3,8 @@
Tasks and projects from the Learn Python course
-[![Stargazers repo roster for @Sinrez/pyCoursera](https://reporoster.com/stars/dark/Sinrez/learn_python_course)](https://github.com/Sinrez/learn_python_course/stargazers) +Если нашли что-то полезное - ставьте звезду! Спасибо =) +
+If you find something useful - put a star! Thank you =) +
+ diff --git a/bank_bottom_proj/Arch Solution.png b/bank_bottom_proj/Arch Solution.png new file mode 100644 index 0000000..f812b45 Binary files /dev/null and b/bank_bottom_proj/Arch Solution.png differ diff --git a/bank_bottom_proj/README.md b/bank_bottom_proj/README.md new file mode 100644 index 0000000..9093894 --- /dev/null +++ b/bank_bottom_proj/README.md @@ -0,0 +1,25 @@ +# Bank Bottom + +Bank Bottom - это проект, агрегирующий статистику по негативным отзывам о банках из сети. +Пока банки из РФ. +Это проект монолитной архитектуры, разработанный на Python. Ядро - Flask + DB SQLlite. Сбор статистики осуществляется по расписанию, через очередь задач в hash-value db Redis с размещением в Docker. +Состоит из web-части, отображающей графики статистики негативных отзвовов за неделю, статистику по категориям банковских продуктов. У проекта есть REST-API, для получения общей статистики негативных и статистики по категориям за неделю. Для проекта создан бот @bank_bottom_bot, возвращающий раз в неделю статистику по отзывам, которую запрашивает через REST-API. +Опционально добавлена возможность публикации отзывов с сохранением отзыва в БД. + +Bank Bottom is a project that aggregates statistics on negative reviews about banks from the internet, currently focusing on banks in Russia. It is a monolithic architecture project developed in Python, with Flask + DB SQLlite as the core. Statistics collection is performed on a schedule, through a task queue in the hash-value db Redis, deployed in Docker. It consists of a web component that displays graphs of negative review statistics for the week and statistics on categories of banking products. The project has a REST API for obtaining overall negative statistics and weekly statistics by categories. Additionally, a bot @bank_bottom_bot was created for the project, which returns weekly statistics on reviews requested via the REST API. Optionally, the ability to publish reviews with the preservation of the review in the database has been added. + +## Истрия проекта +Это Pat-Project. Создан так как многие сайты с банковскими отзывами не публикую статистику по отзывам, искажая информацию о качестве банковских услуг. На этапе MVP собираются негативные отзывы по основным банковским продуктам: Кредиты, Депозиты, Карты, Сервис и т.д. + +This is Pat-Project. It was created because many websites with bank reviews do not publish statistics on reviews, distorting information about the quality of banking services. At the MVP stage, negative reviews are collected for the main banking products: Credits, Deposits, Cards, Services, etc. + +## Архитектура решения + + +## Интерфейсы + +### Bot + + +### UI + diff --git a/bank_bottom_proj/bottom.sqbpro b/bank_bottom_proj/bottom.sqbpro deleted file mode 100644 index 5838f54..0000000 --- a/bank_bottom_proj/bottom.sqbpro +++ /dev/null @@ -1,6 +0,0 @@ --- DELETE from feedback; --- COMMIT - -SELECT bank_name, count(*) as 'count' from feedback -group by bank_name -ORDER by 'count' DESC \ No newline at end of file diff --git a/bank_bottom_proj/bottom_bot/bot_bottom.py b/bank_bottom_proj/bottom_bot/bot_bottom.py index 091e5a5..85ff6fb 100644 --- a/bank_bottom_proj/bottom_bot/bot_bottom.py +++ b/bank_bottom_proj/bottom_bot/bot_bottom.py @@ -1,17 +1,34 @@ #@bank_bottom_bot -from telegram.ext import Updater, CommandHandler, MessageHandler, Filters +import sys + +#это локальные костыли для доступности вспомогательных файлов, добавлять перед импортом основных библиотек +sys.path.append('..') +sys.path.append('/Volumes/D/learn_python_course/bank_bottom_proj/webapp_bottom') +sys.path.append('bank_bottom_proj/webapp_bottom') + +from telegram.ext import Updater, CommandHandler, MessageHandler, Filters, CallbackContext import logging import settings from telegram import ReplyKeyboardMarkup, KeyboardButton from get_banks import get_weekly_bottom, get_all_categories_week +import datetime +from utils import db, get_or_create_user, subscribe_user, unsubscribe_user + msg = 'Я - бот "Дно банки" показываю какие банки 🏦 на этой неделе пробили дно отрицательных отзывов 💩. Жмите кнопки для просмотра статистики:' + keyb = [ [KeyboardButton('Узнать антирейтинг лидеров недели'), KeyboardButton('Узнать антирейтинг по категориям')] ] +# кнопки подписки и отписки +subscribe_button = KeyboardButton('Подписаться') +unsubscribe_button = KeyboardButton('Отписаться') +keyb.append([subscribe_button, unsubscribe_button]) + +# логируем в файл logging.basicConfig( level=logging.INFO, filename='bot.log', @@ -22,6 +39,8 @@ # Функция для обработки команды /start def start_handler(update, context): print('Вызван /start') + #сохраняем пришедшего юзера в бд для работы подписки + user = get_or_create_user(update.effective_user, update.message.chat.id) print(update) user_f_name = update.message.chat.first_name user_l_name = update.message.chat.last_name @@ -45,24 +64,51 @@ def button2_handler(update, context): categories_info += f"{category}:\n{result}\n" categories_info += "\n" update.message.reply_text(categories_info) + +# Функция для отправки сообщения в чат для еженедельной рассылки +def send_weekly_bottom(context: CallbackContext): + chat_id = context.job.context + dictionary = get_weekly_bottom() + result = '\n'.join([f'{key}: {value}' for key, value in dictionary.items()]) + context.bot.send_message(chat_id=chat_id, text=str(result)) +def subscribe(update, context): + user = get_or_create_user(update.effective_user, update.message.chat.id) + subscribe_user(user) + update.message.reply_text('Вы успешно подписались') + +def unsubscribe(update, context): + user = get_or_create_user(update.effective_user, update.message.chat.id) + unsubscribe_user(user) + update.message.reply_text('Вы успешно отписались') # Функция, которая соединяется с платформой Telegram, "тело" нашего бота def main(): try: - mybot = Updater(settings.TOKEN, use_context=True) - - dp = mybot.dispatcher + mybot1 = Updater(settings.TOKEN, use_context=True) + job_queue = mybot1.job_queue + dp = mybot1.dispatcher dp.add_handler(CommandHandler("start", start_handler)) dp.add_handler(MessageHandler(Filters.regex('Узнать антирейтинг лидеров недели'), button1_handler)) - dp.add_handler(MessageHandler(Filters.regex('Узнать рейтинг по категориям'), button2_handler)) + dp.add_handler(MessageHandler(Filters.regex('Узнать антирейтинг по категориям'), button2_handler)) + # обработчики для кнопок подписки и отписки + dp.add_handler(CommandHandler('subscribe', subscribe)) + dp.add_handler(CommandHandler('unsubscribe', unsubscribe)) + dp.add_handler(MessageHandler(Filters.regex('Подписаться'), subscribe)) + dp.add_handler(MessageHandler(Filters.regex('Отписаться'), unsubscribe)) + # Задаем расписание для отправки сообщений + weekly_time = datetime.time(hour=13, minute=0, second=0) + weekday = 4 # Пятница + # Запускаем задачу на повторение еженедельно + #параметр interval установлен на 604800 секунд, что соответствует одной неделе (60 секунд × 60 минут × 24 часа × 7 дней) + job_queue.run_repeating(send_weekly_bottom, interval=604800, first=weekly_time, context=weekday) logging.info('Бот стартовал!') - mybot.start_polling() - mybot.idle() + mybot1.start_polling() + mybot1.idle() except Exception as ex: logging.info(f'Ошибка в функции main: {ex}') print(f'Ошибка в функции main: {ex}') if __name__ == '__main__': - main() + main() \ No newline at end of file diff --git a/bank_bottom_proj/bottom_bot/get_banks.py b/bank_bottom_proj/bottom_bot/get_banks.py index d54fa82..5187a7d 100644 --- a/bank_bottom_proj/bottom_bot/get_banks.py +++ b/bank_bottom_proj/bottom_bot/get_banks.py @@ -3,7 +3,7 @@ import requests as re url_all_week = 'http://localhost:5000//bottom/api/v1.0/weeklyfeedback' -url_all_categories_week = 'http://localhost:5000//bottom/api/v1.0/weeklyfeedback/category' +url_all_categories_week = 'http://127.0.0.1:5000//bottom/api/v1.0/weeklyfeedback/category' def get_weekly_bottom(url=url_all_week): sorted_dict = {} diff --git a/bank_bottom_proj/create_db.py b/bank_bottom_proj/create_db.py index fc1945f..03fcca2 100644 --- a/bank_bottom_proj/create_db.py +++ b/bank_bottom_proj/create_db.py @@ -1,6 +1,3 @@ -# from webapp import db, create_app -# db.create_all(app=create_app()) - from webapp_bottom import create_app from webapp_bottom.model import db diff --git a/bank_bottom_proj/tasks.py b/bank_bottom_proj/tasks.py index b451d89..a26773a 100644 --- a/bank_bottom_proj/tasks.py +++ b/bank_bottom_proj/tasks.py @@ -16,10 +16,8 @@ from celery.schedules import crontab from webapp_bottom.bottom_parser import page_fliper - flask_app = create_app() celery_app = Celery('tasks', broker='redis://localhost:6379/0') -#в broker протокол именно redis:// ... @celery_app.task def banki_content(): @@ -28,8 +26,8 @@ def banki_content(): @celery_app.on_after_configure.connect def setup_periodic_tasks(sender, **kwargs): - sender.add_periodic_task(crontab(minute='*/1'), page_fliper.s()) - # sender.add_periodic_task(crontab(minute='*/10'), page_fliper.s()) + # !тут важно! ссылаемся на определённую выше функцию задачи! + sender.add_periodic_task(crontab(minute='*/20'), banki_content.s()) # 10 мин чтобы не дудосить # для запуска по пятницам в 16:30 - # sender.add_periodic_task( crontab(hour=16, minute=30, day_of_week=5), bottom_parser.page_fliper.s()) \ No newline at end of file + # sender.add_periodic_task(crontab(hour=16, minute=30, day_of_week=5), banki_content.s()) diff --git a/bank_bottom_proj/tg.png b/bank_bottom_proj/tg.png new file mode 100644 index 0000000..ecfa839 Binary files /dev/null and b/bank_bottom_proj/tg.png differ diff --git a/bank_bottom_proj/ui.png b/bank_bottom_proj/ui.png new file mode 100644 index 0000000..e307f23 Binary files /dev/null and b/bank_bottom_proj/ui.png differ diff --git a/bank_bottom_proj/webapp_bottom/__init__.py b/bank_bottom_proj/webapp_bottom/__init__.py index 7286f39..16c6712 100644 --- a/bank_bottom_proj/webapp_bottom/__init__.py +++ b/bank_bottom_proj/webapp_bottom/__init__.py @@ -1,14 +1,23 @@ # ./run.sh # source env_bottom/bin/activate # export FLASK_APP=webapp_bottom && flask db init +import sys -from flask import Flask, render_template, jsonify +#это локальные костыли для доступности вспомогательных файлов, добавлять перед импортом основных библиотек +sys.path.append('..') +sys.path.append('/Volumes/D/learn_python_course/bank_bottom_proj/webapp_bottom') + +from flask import Flask, render_template, jsonify, request from webapp_bottom.model import db, Feedback from flask_migrate import Migrate import datetime import pandas as pd import plotly.graph_objs as go from webapp_bottom.config import categories +from utils import generate_short_id_resource ,save_response +from wtforms.validators import DataRequired, Length, ValidationError +from flask_wtf import FlaskForm, csrf +from wtforms import StringField, SubmitField def create_app(): app = Flask(__name__) @@ -16,18 +25,20 @@ def create_app(): db.init_app(app) migrate = Migrate(app, db) - def get_count_from_db(days=10, cat = ''): + def get_count_from_db(days=7, cat = ''): delta = datetime.timedelta(days) now_day = datetime.date.today() delta_days = now_day - delta + print(delta_days) date_obj = datetime.datetime.strptime(str(delta_days), '%Y-%m-%d') formatted_date_str = date_obj.strftime('%d.%m.%Y') + print(formatted_date_str) # в зависимости наличия катеогории или возвращаем все отзывы или по категориям if not cat: - qr = db.session.query(Feedback.bank_name,db.func.count(Feedback.url_page)).filter(Feedback.response_date >= formatted_date_str).group_by(Feedback.bank_name).having(db.func.count(Feedback.url_page) > 10).order_by(db.func.count(Feedback.url_page).desc()).all() + qr = db.session.query(Feedback.bank_name,db.func.count(Feedback.url_page)).filter(Feedback.response_date >= formatted_date_str).group_by(Feedback.bank_name).having(db.func.count(Feedback.url_page) >= 1).order_by(db.func.count(Feedback.url_page).desc()).all() return qr else: - qr = db.session.query(Feedback.bank_name,db.func.count(Feedback.url_page)).filter(Feedback.response_date >= formatted_date_str).filter(Feedback.category == cat).group_by(Feedback.bank_name).having(db.func.count(Feedback.url_page) > 5).order_by(db.func.count(Feedback.url_page).desc()).all() + qr = db.session.query(Feedback.bank_name,db.func.count(Feedback.url_page)).filter(Feedback.response_date >= formatted_date_str).filter(Feedback.category == cat).group_by(Feedback.bank_name).having(db.func.count(Feedback.url_page) >= 1).order_by(db.func.count(Feedback.url_page).desc()).all() return qr @app.route('/bottom/api/v1.0/weeklyfeedback', methods=['GET']) @@ -59,6 +70,14 @@ def get_feedback(): news = Feedback.query.order_by(Feedback.response_date.desc()).all() return render_template('feedback.html', page_title=title, news_list=news) + @app.route("/") + def get_local_feedback(url): + title = 'Дно банки' + news = Feedback.query.filter_by(url_page='/'+url).first() + print(news) + print(type(news)) + return render_template('local_feedback.html', page_title=title, news_list=news) + @app.route("/categories") def get_categories(categories: list = categories): bank_dict = {} @@ -72,4 +91,43 @@ def get_categories(categories: list = categories): bank_dict[cat] = [qr,plot_div] return render_template('categories.html', page_title=title, banks_dict=bank_dict) + def validate_input(form, field): + errors = [] + excluded_chars = "*?!'^+%&;/()=}][{$#" + for char in field.data: + if char in excluded_chars: + errors.append(f'Символ {char} запрещен для ввода!') + if errors: + raise ValidationError(*errors) + + class FeedbackForm(FlaskForm): + bank = StringField('Банк', validators=[DataRequired(), Length(min=2, max=50), validate_input]) + theme = StringField('Тема', validators=[DataRequired(), Length(min=2, max=100),validate_input]) + category = StringField('Категория', validators=[DataRequired()]) + review = StringField('Отзыв', validators=[DataRequired(), Length(min=2, max=3000),validate_input]) + city = StringField('Город', validators=[DataRequired(), Length(min=2, max=50),validate_input]) + submit = SubmitField('Отправить') + + @app.route('/post_feedback', methods=['GET', 'POST']) + def post_feedback(): + title = 'Дно банки' + form = FeedbackForm() + if form.validate_on_submit(): + form.csrf_token.data = csrf.generate_csrf() + print("Data passed validation!") + bank = form.bank.data + theme = form.theme.data + category = form.category.data + review = form.review.data + city = form.city.data + id_res_in = generate_short_id_resource(review) + id_page_in = '/' + id_res_in + response_date_in = datetime.datetime.now() + save_response(id_res_in, id_page_in, bank, category, theme, response_date_in, city, review) + print(f'Отзыв добавлен {id_page_in}, {review}') + return "Отзыв отправлен!" + else: + print(form.errors) + return render_template('post_feedback.html', page_title=title, form=form) + return app \ No newline at end of file diff --git a/bank_bottom_proj/webapp_bottom/bottom_parser.py b/bank_bottom_proj/webapp_bottom/bottom_parser.py index 274d3da..8f6d0f4 100644 --- a/bank_bottom_proj/webapp_bottom/bottom_parser.py +++ b/bank_bottom_proj/webapp_bottom/bottom_parser.py @@ -1,11 +1,9 @@ # source env_bottom/bin/activate - import sys #это локальные костыли для доступности вспомогательных файлов, добавлять перед импортом основных библиотек sys.path.append('..') sys.path.append('/Volumes/D/learn_python_course/bank_bottom_proj/webapp_bottom') -sys.path.append('bank_bottom_proj/webapp_bottom') from bs4 import BeautifulSoup from time import sleep @@ -15,19 +13,22 @@ from config import categories from check_resource import check_url -def page_fliper(categories: list = categories, start: int = 1, limit: int = 4) -> None: +def page_fliper(categories: list = categories, start: int = 1, limit: int = 3) -> None: # limit = 150 #1440 - c начала 22 - # limit = 4 # !!!cтавим на период теста чтобы не дудосить!!! + # limit = 3 или 4 # !!!cтавим на период теста чтобы не дудосить!!! url_base_site = 'https://www.banki.ru' check_url(url_base_site) for cat in categories: + print(f'Обрабатываю категорию {cat}') + print() #цикл перебора следуюших страниц, так как есть параметр page= и с ним не вытащить ссылки из цикла выше for l in range(start, limit+1): url_categor_history = f'https://www.banki.ru/services/responses/list/product/{cat}/?page={l}&is_countable=on&rate[]=1&rate[]=2' + print(url_categor_history) for url in urls_parser(url_categor_history,url_base_site): - check_url(url) if url: + # print(url) try: *result, = page_parser(url, cat) save_response(*result) @@ -35,7 +36,8 @@ def page_fliper(categories: list = categories, start: int = 1, limit: int = 4) - print(''.join(result)) print(f'Ошибка: {te}') exit() - sleep(randint(1,2)) + sleep(randint(2,3)) + sleep(randint(1,2)) def urls_parser(url_in: str, url_base_site: str) -> list: url_from_parse = [] @@ -62,6 +64,10 @@ def page_parser(url_page: str, category: str ='') -> tuple: #получаем название банка bank_na = bs2.find('img', class_='lazy-load') bank_name = str(bank_na).split('"')[1].replace('alt="','').replace('"','') + #на этом шаге название банка может отстустовать как alt текст картинки логотипа, тогда парсится Банки.ру, поэтому: + if bank_name == 'Банки.ру': + bank_na = bs2.find('span', class_='link-simple link-simple--theme_major-gray').text.strip() + bank_name = bank_na #получаем полный отзыв response_full = bs2.find('div', class_='lb1789875 markdown-inside markdown-inside--list-type_circle-fill').text.strip() #получаем дату отзыва @@ -72,7 +78,7 @@ def page_parser(url_page: str, category: str ='') -> tuple: #получаем id отзыва id_url = url_page.split('/')[7] except AttributeError as ar: - return f'Произошла ошибка в блоке BeautifulSoup парсинга: {ar}' + return f'Произошла ошибка в блоке BeautifulSoup парсинга: {ar} на {url_page}' except Exception as ex: return f'Произошла ошибка в функции page_parser: {ex}' return id_url, url_page, bank_name, category, short_feedback, response_date, response_city, response_full diff --git a/bank_bottom_proj/webapp_bottom/model.py b/bank_bottom_proj/webapp_bottom/model.py index 3d737a6..ec22f1d 100644 --- a/bank_bottom_proj/webapp_bottom/model.py +++ b/bank_bottom_proj/webapp_bottom/model.py @@ -13,4 +13,14 @@ class Feedback(db.Model): response_full = db.Column(db.Text, nullable=True) def __repr__(self): - return f' Отзыв: {self.bank_name}, {self.category}, {self.short_feedback}, {self.url_page}' \ No newline at end of file + return f' Отзыв: {self.bank_name}, {self.category}, {self.short_feedback}, {self.url_page}' + +class User(db.Model): + id_user = db.Column(db.String,unique=True, primary_key=True, nullable=False) + first_name = db.Column(db.String, nullable=True) + last_name = db.Column(db.String, nullable=True) + user_name = db.Column(db.String, nullable=True) + chat_id = db.Column(db.String,unique=True, nullable=False) + subscribed = db.Column(db.Boolean, default=False) + + \ No newline at end of file diff --git a/bank_bottom_proj/webapp_bottom/sravni_parser_v2.py b/bank_bottom_proj/webapp_bottom/sravni_parser_v2.py index fef927f..d340ccd 100644 --- a/bank_bottom_proj/webapp_bottom/sravni_parser_v2.py +++ b/bank_bottom_proj/webapp_bottom/sravni_parser_v2.py @@ -54,9 +54,14 @@ def page_parser(url_page: str, categor: str =''): response_dt = bs2.find('div', class_ ='_1n8o0h2 _vea58f _pbfp49').find('div',class_='h-color-D30 _1h41p0x').text.strip() response_date = datetime.strptime(response_dt, '%d %B %Y') except ValueError as ve: - current_year = str(datetime.now().year) - response_dt_in = response_dt+' '+current_year - response_date = datetime.strptime(response_dt_in, '%d %B %Y') + #сайт не следит за форматом даты, тут обрабатываем случай, когда вместо даты указано только время вида hh:mm + # <6 чтоб не проскочили даты вида dd:mm:yy, только dd:mm + if ':' in response_dt and len(response_dt) < 6: + response_date = datetime.now() + else: + current_year = str(datetime.now().year) + response_dt_in = response_dt+' '+current_year + response_date = datetime.strptime(response_dt_in, '%d %B %Y') # #получаем город response_city = bs2.find('div', class_ ='h-color-D30 h-ml-8 _1h41p0x _1gpt55s').text.strip() # #получаем id отзыва - 00 чтобы исключить совпадений с id отзывов из других ресурсов @@ -73,7 +78,7 @@ def page_fliper(categor): url_base_site = 'https://www.sravni.ru' check_url(url_base_site) for cat in categor: - url_base = f'https://www.sravni.ru/banki/otzyvy/?tag={cat}&rated=one&rated=two&rated=three' + url_base = f'https://www.sravni.ru/banki/otzyvy/?tag={cat}&rated=one&rated=two' url_from_parse = (url_parser(url_base, url_base_site)) if isinstance(url_from_parse, set) and len(url_from_parse) > 0: res_url[cat] = url_from_parse @@ -99,7 +104,10 @@ def page_fliper(categor): if __name__ == '__main__': i = 10 while i >=0: + print(f'Попытка {11-i}') + print('+'*20) page_fliper(sravni_categories) + print('+'*20) sleep(randint(3,4)) i -= 1 diff --git a/bank_bottom_proj/webapp_bottom/templates/index.html b/bank_bottom_proj/webapp_bottom/templates/index.html index 0644d93..f58bd1a 100644 --- a/bank_bottom_proj/webapp_bottom/templates/index.html +++ b/bank_bottom_proj/webapp_bottom/templates/index.html @@ -29,6 +29,9 @@

Динамика погружения на дно за неделю

+

Лидеры дна

diff --git a/bank_bottom_proj/webapp_bottom/templates/local_feedback.html b/bank_bottom_proj/webapp_bottom/templates/local_feedback.html new file mode 100644 index 0000000..25366e6 --- /dev/null +++ b/bank_bottom_proj/webapp_bottom/templates/local_feedback.html @@ -0,0 +1,52 @@ + + + + + + + + + + + {{ page_title }} + + +
+

{{ page_title }}

+ +
+
+ {% with messages = get_flashed_messages() %} + {% if messages %} + + {% endif %} + {% endwith %} + +

Отзыв:

+
+
+

Краткое содержание: {{ news_list.short_feedback }}

+
+ {% if news_list %} +
+

Дата: {{ news_list.response_date.strftime('%d.%m.%Y') }}

+

Категория: {{ news_list.category }}

+

Банк: {{ news_list.bank_name }}

+

Город: {{ news_list.city }}

+

Отзыв: {{ news_list.review }}

+
+ {% endif %} +
+
+
+
+ + + + + + + + + + diff --git a/bank_bottom_proj/webapp_bottom/utils.py b/bank_bottom_proj/webapp_bottom/utils.py index 925ebcc..c589267 100644 --- a/bank_bottom_proj/webapp_bottom/utils.py +++ b/bank_bottom_proj/webapp_bottom/utils.py @@ -2,14 +2,16 @@ from fake_useragent import UserAgent UserAgent().chrome from flask import Flask -from model import db, Feedback +from model import db, Feedback, User from config import SQLALCHEMY_DATABASE_URI - +import hashlib +from sqlalchemy.exc import SQLAlchemyError +from flask_sqlalchemy import SQLAlchemy def get_url(url_page:str): ua = UserAgent() fake_ua = {'User-Agent': UserAgent().chrome, - 'Referer': 'https://www.ya.ru/'} + 'Referer': 'https://www.ya.ru/'} if 'sravni' in url_page: try: resp_for_cookeis = req.get("https://www.sravni.ru/") @@ -29,13 +31,69 @@ def get_url(url_page:str): def save_response(id_url, url_page, bank_name, category, short_feedback, response_date, response_city, response_full) -> None: app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = SQLALCHEMY_DATABASE_URI + try: + with app.app_context(): + try: + db.init_app(app) + url_exists = Feedback.query.filter(Feedback.url_page == url_page).count() + if not url_exists: + new_feedback = Feedback(id_url=id_url, url_page=url_page, bank_name=bank_name, category=category, + short_feedback=short_feedback,response_date=response_date,response_city=response_city, + response_full=response_full) + db.session.add(new_feedback) + db.session.commit() + except SQLAlchemyError as sq: + print(f'Ошибка sqlalchemy записи в save_response в бд: {sq}') + except Exception as ex0: + print(f'Ошибка в utlils.save_response: {ex0}') + + +def generate_short_id_resource(feedb): + # Получаем хэш от URL с использованием sha256 + hash_object = hashlib.sha256(feedb.encode()) + hex_dig = hash_object.hexdigest() + + # Оставляем только первые 8 символов хэша и возвращаем его + short_hash = hex_dig[:8] + return short_hash + +def get_or_create_user(effective_user, chat_id) -> None: + app = Flask(__name__) + app.config['SQLALCHEMY_DATABASE_URI'] = SQLALCHEMY_DATABASE_URI + try: + with app.app_context(): + try: + db.init_app(app) + user_exists = User.query.filter(User.id_user == effective_user.id).first() + if not user_exists: + new_user = User(id_user=effective_user.id, first_name = effective_user.first_name, + last_name = effective_user.last_name,user_name = effective_user.username, chat_id = chat_id) + db.session.add(new_user) + db.session.commit() + return user_exists + except SQLAlchemyError as sq: + print(f'Ошибка sqlalchemy записи пользователя tg в get_or_create_user в бд: {sq}') + except Exception as ex1: + print(f'Ошибка в utlils.save_response: {ex1}') + +def subscribe_user(user_data): + app = Flask(__name__) + db = SQLAlchemy() + app.config['SQLALCHEMY_DATABASE_URI'] = SQLALCHEMY_DATABASE_URI + db.init_app(app) + with app.app_context(): + if user_data.subscribed is False or user_data.subscribed is None: + db.session.query(User).filter(User.id_user == user_data.id_user).update({"subscribed": True}) + db.session.flush() + db.session.commit() + +def unsubscribe_user(user_data): + app = Flask(__name__) + db = SQLAlchemy() + app.config['SQLALCHEMY_DATABASE_URI'] = SQLALCHEMY_DATABASE_URI + db.init_app(app) with app.app_context(): - # @todo добавить обработку sqlalchemy.exc.OperationalError - db.init_app(app) - url_exists = Feedback.query.filter(Feedback.url_page == url_page).count() - if not url_exists: - new_feedback = Feedback(id_url=id_url, url_page=url_page, bank_name=bank_name, category=category, - short_feedback=short_feedback,response_date=response_date,response_city=response_city, - response_full=response_full) - db.session.add(new_feedback) - db.session.commit() \ No newline at end of file + if user_data.subscribed: + db.session.query(User).filter(User.id_user == user_data.id_user).update({"subscribed": False}) + db.session.flush() + db.session.commit() diff --git a/dogs_around/.gitignore b/dogs_around/.gitignore new file mode 100644 index 0000000..c4197bf --- /dev/null +++ b/dogs_around/.gitignore @@ -0,0 +1,39 @@ +*.pyc +*.egg-info + +# Если вы программируете на Mac +.DS_Store +../.DS_Store + +# Если вы пользуетесь виртуальными окружениями +/env +/env_dogs +/res.txt +/bs.txt + +# Настройки и хранение секретных данных +/.env +/.env_dogs +settings.py +*.log +.db +.vscode/ +.idea/ +env/ +.DS_Store +__pycache__/ +config.py +__pycache__/ +.vscode/ +env/ +drafts +vscode +DS_Store +dogs.db +dogs +celerybeat-schedule.db +celerybeat-schedule +.sqbpro +/.sqbpro +.sqbpro/ +sqbpro diff --git a/dogs_around/README.md b/dogs_around/README.md new file mode 100644 index 0000000..8f3b2bf --- /dev/null +++ b/dogs_around/README.md @@ -0,0 +1,18 @@ +# Dogs Around + +Собакруг- это pat-проект, MVP0, социальной сети для собак. Основная идея - объединение собаководов для совместного выгула собак, концепт может дорабатываться. Идея "круга" - возможное объединение по близким локациям для выгула. +Это проект монолитной архитектуры, разработанный на Python. Ядро - Flask + DB SQLlite. + +На этапе MVP0 реализован основной функционал: добавление пользователя, добавление его собак(и), добавление в друзья других собак. Реализована функция аутентификации, валидации полей ввода. + +UI выполнен достаточно просто, так как основная цель проекта была отработать функционирования back-end части. +Соц сеть может быть адаптирована не только для собак, но и доработана под другие предметные области. + + +DogsAround is a pat project, MVP0, a social network for dogs. The main idea is to unite dog breeders for joint dog walking, the concept can be finalized. +This is a monolithic architecture project developed in Python. The core is Flask + DB Sqlite. + +At the MVP 0 stage, the main functionality is implemented: adding a user, adding his dogs (and), adding other dogs to friends. The function of authentication, validation of input fields is implemented. + +The UI is quite simple, since the main goal of the project was to work out the functioning of the back-end part. +The social network can be adapted not only for dogs, but also modified for other subject areas. \ No newline at end of file diff --git a/dogs_around/create_db.py b/dogs_around/create_db.py new file mode 100644 index 0000000..39012a7 --- /dev/null +++ b/dogs_around/create_db.py @@ -0,0 +1,11 @@ +#это локальные костыли для доступности вспомогательных файлов, добавлять перед импортом основных библиотек +import sys +sys.path.append('..') +sys.path.append('/Volumes/D/learn_python_course/dogs_around/webapp_dogs') + +from webapp_dogs import create_app +from webapp_dogs.model import db + +app = create_app() +with app.app_context(): + db.create_all() \ No newline at end of file diff --git a/dogs_around/requirements.txt b/dogs_around/requirements.txt new file mode 100644 index 0000000..aa91330 --- /dev/null +++ b/dogs_around/requirements.txt @@ -0,0 +1,31 @@ +certifi==2022.12.7 +charset-normalizer==3.0.1 +click==8.1.3 +Flask==2.2.3 +Flask-Login==0.6.2 +Flask-SQLAlchemy==3.0.3 +Flask-WTF==1.1.1 +idna==3.4 +itsdangerous==2.1.2 +Jinja2==3.1.2 +MarkupSafe==2.1.2 +requests==2.28.2 +soupsieve==2.4 +SQLAlchemy==2.0.4 +typing_extensions==4.5.0 +urllib3==1.26.14 +Werkzeug==2.2.3 +WTForms==3.0.1 +flask-migrate==4.0.4 +bokeh==3.1.0 +plotly==5.13.1 +cffi==1.14.0 +cryptography==2.9.2 +decorator==4.4.2 +future==0.18.2 +pycparser==2.20 +PySocks==1.7.1 +python-telegram-bot==12.7 +six==1.14.0 +tornado==6.0.4 +emoji==0.5.4 diff --git a/dogs_around/run.sh b/dogs_around/run.sh new file mode 100755 index 0000000..29520ef --- /dev/null +++ b/dogs_around/run.sh @@ -0,0 +1,2 @@ +#!/bin/sh +export FLASK_APP=webapp_dogs && export FLASK_ENV=development && flask run \ No newline at end of file diff --git a/dogs_around/webapp_dogs/__init__.py b/dogs_around/webapp_dogs/__init__.py new file mode 100644 index 0000000..8256918 --- /dev/null +++ b/dogs_around/webapp_dogs/__init__.py @@ -0,0 +1,214 @@ +# source env_dogs/bin/activate +# ./run.sh + +from flask import Flask, render_template, flash, redirect, url_for,request, session, jsonify, abort +from datetime import datetime +from wtforms.validators import DataRequired, Length, ValidationError +from flask_wtf import FlaskForm, csrf +from wtforms import StringField, SubmitField +from webapp_dogs import config +from werkzeug.security import generate_password_hash +from webapp_dogs.model import db, User, Dog, get_user_dogs, Friendship, FrendStatusEnum +from webapp_dogs.forms import RegistrationForm, DogForm , LoginForm +from webapp_dogs.utils_dog import generate_id_user, get_or_create_user, generate_id_dog, get_or_create_dog +from flask_login import LoginManager,login_user, logout_user, current_user, login_required + +def create_app(): + app = Flask(__name__) + app.secret_key = config.SECRET_KEY + app.config.from_pyfile('config.py') + db.init_app(app) + + login_manager = LoginManager() + login_manager.init_app(app) + login_manager.login_view = 'login' + + @login_manager.user_loader + def load_user(user_id): + return User.query.get(user_id) + + @app.route("/") + def index(): + title = 'Собакруг' + dogs = Dog.query.order_by(Dog.response_date.desc()).all() + return render_template('index.html', page_title=title, dogs= dogs) + + @app.route("/cabinet") + def cabinet(): + title = 'Мой профиль' + email = session.get('email') + dogs = Dog.query.order_by(Dog.response_date.desc()).all() + print(dogs) + my_dogs = get_user_dogs(email) + res_my_dogs = ', '.join([dog.name_dog for dog in my_dogs]) + return render_template('cabinet.html', page_title=title, dogs= dogs, my_dogs = res_my_dogs, email = email) + + @app.route("/profile") + def profile(): + email = session.get('email') + user = User.query.filter_by(email=email).first() + title = 'Мой профиль' + + return render_template('profile.html', page_title=title, user=user) + + @app.route('/dog/') + def profile_dog(dog_id): + email = session.get('email') + dog = Dog.query.filter_by(id_dog=dog_id).first() + title = 'Профиль собачки' + return render_template('profile_dog.html', page_title=title, dog=dog) + + @app.route('/add_friend/', methods=['POST']) + @login_required + def add_friend(dog_id): + # user_id = session.get('user_id') + email = session.get('email') + my_dog = get_user_dogs(email)[0] + dog = Dog.query.get(dog_id) + if dog is None: + abort(404) + print(current_user.id_user) + print(my_dog) + if my_dog: + friendship_request = Friendship(sender_dog_id=my_dog.id_dog, receiver_dog_id=dog_id) + db.session.add(friendship_request) + db.session.commit() + flash(f'Вы отправили запрос на дружбу {dog.name_dog}!', 'success') + else: + flash('Произошла ошибка! Попробуйте еще раз.', 'danger') + return redirect(url_for('user_dogs')) + + @app.route('/accept_request/') + def accept_request(id_dog): + request = Friendship.query.filter_by(sender_dog_id=id_dog, status=FrendStatusEnum.pending.value).first() + if request: + request.accept_request() + return redirect(url_for('user_dogs')) + + @app.route('/reject_request/') + def reject_request(id_dog): + request = Friendship.query.filter_by(sender_dog_id=id_dog, status=FrendStatusEnum.pending.value).first() + if request: + request.decline_request() + return redirect(url_for('user_dogs')) + + + @app.route('/login', methods=['GET', 'POST']) + def login(): + title = "Авторизация" + form = LoginForm() + email = None + + if request.method == 'POST' and form.validate(): + user = User.query.filter_by(email=form.email.data).first() + if user and user.check_password(form.password.data): + login_user(user) # авторизация пользователя + print(user.get_mail()) + email = user.get_mail() + session['email'] = email + user_id = user.get_id() + session['user_id'] = user_id + flash('Вы успешно авторизовались!', 'success') + next_page = request.args.get('next') # получаем параметр next из URL + if next_page: + return redirect(next_page) # если параметр есть, переходим по нему + else: + return redirect(url_for('cabinet')) + else: + flash('Неправильное имя пользователя или пароль', 'danger') + + return render_template('login.html', form=form, title=title, email=email) + + @app.route('/logout') + def logout(): + logout_user() + flash('Вы успешно разлогинились') + return redirect(url_for('index')) + + @app.route('/registration', methods=['GET', 'POST']) + def registration(): + form = RegistrationForm() + if request.method == 'POST' and form.validate_on_submit(): + # Получение данных из формы + id_user = generate_id_user() + first_name = request.form['first_name'] + last_name = request.form['last_name'] + user_name = request.form['user_name'] + email = request.form['email'] + password = request.form['password'] + confirm_password = request.form['confirm_password'] + + # Валидация данных + error = None + if not first_name: + error = 'Имя пользователя обязательно для заполнения.' + elif not last_name: + error = 'Фамилия пользователя обязательна для заполнения.' + elif not password: + error = 'Пароль обязателен для заполнения.' + elif password != confirm_password: + error = 'Пароли не совпадают.' + + if error is None: + # Сохранение данных в бд + hashed_password = generate_password_hash(password) + res = get_or_create_user(id_user,first_name, last_name, user_name, email, hashed_password) + if res: + return redirect(url_for('login')) + # Если данные не прошли валидацию, показываем ошибки пользователю + return render_template('registration.html', error=error) + else: + print(form.errors) + # Если метод запроса GET, просто отображаем шаблон + return render_template('registration.html', form=form) + + @app.route('/user_dogs', methods=['GET']) + def user_dogs(): + email = session.get('email') # получаем email пользователя из запроса + print(email) + user_dogs = get_user_dogs(email) + print(user_dogs) + friend_requests = [dog.get_friend_requests() for dog in user_dogs][0] + print(f'Список запросов в друзья: {friend_requests}') + friends = [dog.get_friends() for dog in user_dogs][0] + print(friends) + if user_dogs: + return render_template('user_dogs.html', dogs= user_dogs, email = email, friend_requests = friend_requests, friends= friends) + else: + return {'message': 'User not found'}, 404 # возвращаем сообщение об ошибке, если пользователь не найден + + @app.route('/register_dog', methods=['GET', 'POST']) + @login_required + def register_dog(): + form = DogForm() + if request.method == 'POST' and form.validate_on_submit(): + username = current_user.email + print(username) + name_dog = form.name_dog.data + age_dog = form.age_dog.data + breed_dog = form.breed_dog.data + response_date = datetime.now() + city_dog = form.city_dog.data + foto_dog = form.foto_dog.data + voice_dog = form.voice_dog.data + id_dog = generate_id_dog(name_dog, age_dog, breed_dog, response_date, city_dog) + + # Валидация данных + error = None + if not name_dog: + error = 'Кличка собаки обязательна для заполнения.' + elif not age_dog: + error = 'Возраст собаки обязателен для заполнения.' + elif not breed_dog: + error = 'Порода обязательна для заполнения.' + elif not city_dog: + error = 'Город обязателе для заполнения.' + + if error is None: + get_or_create_dog(id_dog,name_dog, age_dog, breed_dog, response_date,city_dog, foto_dog, voice_dog, username) + flash('Респекты, ваш песель зарегистрирован!') + return redirect(url_for('cabinet')) + else: + print(form.errors) + return render_template('register_dog.html', form=form) + return app \ No newline at end of file diff --git a/dogs_around/webapp_dogs/forms.py b/dogs_around/webapp_dogs/forms.py new file mode 100644 index 0000000..309d79b --- /dev/null +++ b/dogs_around/webapp_dogs/forms.py @@ -0,0 +1,28 @@ +from flask_wtf import FlaskForm, CSRFProtect +from wtforms import StringField, PasswordField, SubmitField +from wtforms.validators import DataRequired, EqualTo, Email, Length + +csrf = CSRFProtect() + +class LoginForm(FlaskForm): + email = StringField('Почта пользователя', validators=[DataRequired()]) + password = PasswordField('Пароль', validators=[DataRequired()]) + submit = SubmitField('Отправить') + +class RegistrationForm(FlaskForm): + first_name = StringField('Имя пользователя', validators=[DataRequired(), Length(max=100)]) + last_name = StringField('Фамилия пользователя', validators=[DataRequired(),Length(max=200)]) + user_name = StringField('Ник пользователя', validators=[DataRequired()]) + email = StringField('Email', validators=[DataRequired(), Length(max=120)]) + password = PasswordField('Пароль', validators=[DataRequired()]) + confirm_password = PasswordField('Подтвердите пароль', validators=[DataRequired(), EqualTo('password')]) + submit = SubmitField('Зарегистрироваться') + +class DogForm(FlaskForm): + name_dog = StringField('Имя собаки', validators=[DataRequired()]) + age_dog = StringField('Возраст собаки', validators=[DataRequired()]) + breed_dog = StringField('Порода собаки', validators=[DataRequired()]) + city_dog = StringField('Город', validators=[DataRequired()]) + foto_dog = StringField('Фото собаки', validators=[DataRequired()]) + voice_dog = StringField('Голос собаки', validators=[DataRequired()]) + submit = SubmitField('Отправить') diff --git a/dogs_around/webapp_dogs/model.py b/dogs_around/webapp_dogs/model.py new file mode 100644 index 0000000..2db71bc --- /dev/null +++ b/dogs_around/webapp_dogs/model.py @@ -0,0 +1,127 @@ +from flask_sqlalchemy import SQLAlchemy +import enum +from werkzeug.security import check_password_hash +from flask_login import UserMixin + +db = SQLAlchemy() + +def get_user_dogs(email): + user = User.query.filter_by(email=email).first() # ищем пользователя в базе данных по email + if user: + dogs = user.dogs # получаем список собак, связанных с данным пользователем + print(dogs) + return dogs + +#для обарботки статусов дружбы accepted, declined, friendship +class FrendStatusEnum(enum.Enum): + pending = 0 + accepted = 1 + friendship = 2 + declined = 3 + +# таблица для связи многие-ко многим владелец-собака, так как у собаки может быть более 1 владельца +association_table = db.Table('association', + db.Column('user_id', db.String, db.ForeignKey('user.id_user')), + db.Column('dog_id', db.String, db.ForeignKey('dog.id_dog')) +) + +# а этот класс для отображаения "дружбы" между собакой и собакой/собаками пользователя через пользовтеля класс User +# status может принимать варианты accepted, declined, friendship - хранятся в config.py +class Friendship(db.Model): + id = db.Column(db.Integer, primary_key=True) + sender_dog_id = db.Column(db.String, db.ForeignKey('dog.id_dog')) + receiver_dog_id = db.Column(db.String, db.ForeignKey('dog.id_dog')) + status = db.Column(db.String, default=FrendStatusEnum.pending.value) + + def accept_request(self): + self.status = FrendStatusEnum.accepted.value + db.session.commit() + + def decline_request(self): + self.status = FrendStatusEnum.declined.value + db.session.commit() + + @classmethod + def add_friendship(cls, sender_dog, receiver_user): + # Создаем новый объект Friendship + friendship = cls(sender_dog_id=sender_dog.id_dog, receiver_dog_id=receiver_user.dogs[0].id_dog) + # Добавляем его в сессию для сохранения в базе данных + db.session.add(friendship) + db.session.commit() + + +class Dog(db.Model): + id_dog = db.Column(db.String, unique=True, primary_key=True, nullable=False) + name_dog = db.Column(db.String, nullable=False) + age_dog = db.Column(db.Integer, nullable=False, default=0) + breed_dog = db.Column(db.String, nullable=False) + response_date = db.Column(db.DateTime, nullable=False) + city_dog = db.Column(db.String, nullable=True) + foto_dog = db.Column(db.String, nullable=True) + voice_dog = db.Column(db.String, nullable=True) + users = db.relationship('User', secondary=association_table, back_populates='dogs') + + def add_friend_request(self, user_id): + friendship = Friendship(sender_dog_id=self.id_dog, receiver_dog_id=user_id) + db.session.add(friendship) + db.session.commit() + + def get_friends(self): + friends = [] + friendships = Friendship.query.filter_by(status=FrendStatusEnum.accepted.value).all() + for friendship in friendships: + if friendship.sender_dog_id == self.id_dog: + friend = Dog.query.filter_by(id_dog=friendship.receiver_dog_id).first() + if friend: + friends.append(friend) + elif friendship.receiver_dog_id == self.id_dog: + friend = Dog.query.filter_by(id_dog=friendship.sender_dog_id).first() + if friend: + friends.append(friend) + return friends + + def get_friend_requests(self): + friend_requests = [] + requests_from_user = Friendship.query.filter_by(receiver_dog_id=self.id_dog, status=FrendStatusEnum.pending.value).all() + print(requests_from_user) + for req in requests_from_user: + print(f'req.sender_dog_id: {req.sender_dog_id}') + dog = Dog.query.filter_by(id_dog=req.sender_dog_id).first() + print(f'DOG {dog}') + if dog: + friend_requests.append(dog) + print(f'Список DOG {friend_requests}') + return friend_requests + + + def __repr__(self): + return f'{self.id_dog}, {self.name_dog}, {self.breed_dog}, {self.age_dog}' + + + +class User(db.Model): + id_user = db.Column(db.String,unique=True, primary_key=True, nullable=False) + first_name = db.Column(db.String, nullable=False) + last_name = db.Column(db.String, nullable=False) + user_name = db.Column(db.String, nullable=True) + email = db.Column(db.String, nullable=False) + password = db.Column(db.String, nullable=False) + chat_id = db.Column(db.String,unique=True, nullable=True) + subscribed = db.Column(db.Boolean, default=False, nullable=True) + dogs = db.relationship('Dog', secondary=association_table, back_populates='users') + + def check_password(self, password): + return check_password_hash(self.password, password) + + def is_active(self): + # возвращает True, если пользователь активен, иначе - False + return True + + def get_id(self): + return str(self.id_user) + + def get_mail(self): + return str(self.email) + + def is_authenticated(self): + return True \ No newline at end of file diff --git a/dogs_around/webapp_dogs/static/logo.png b/dogs_around/webapp_dogs/static/logo.png new file mode 100644 index 0000000..8300489 Binary files /dev/null and b/dogs_around/webapp_dogs/static/logo.png differ diff --git a/dogs_around/webapp_dogs/static/logo_res.png b/dogs_around/webapp_dogs/static/logo_res.png new file mode 100644 index 0000000..0608b93 Binary files /dev/null and b/dogs_around/webapp_dogs/static/logo_res.png differ diff --git a/dogs_around/webapp_dogs/templates/cabinet.html b/dogs_around/webapp_dogs/templates/cabinet.html new file mode 100644 index 0000000..eb37bdc --- /dev/null +++ b/dogs_around/webapp_dogs/templates/cabinet.html @@ -0,0 +1,181 @@ + + + + + + + {{page_title}} + + + + + + {% with messages = get_flashed_messages() %} + {% if messages %} +
    + {% for message in messages %} +
  • {{ message }}
  • + {% endfor %} +
+ {% endif %} + {% endwith %} +
  • Пользователь: {{ email }}
  • +
  • Мои собачки: {{ my_dogs }}
  • + +
    + {% for dog in dogs %} +
    +
    + {{ dog.name_dog }} +
    +
    +

    Кличка: {{ dog.name_dog }}

    +

    Порода: {{ dog.breed_dog }}

    +

    Возраст: {{ dog.age_dog }}

    +
    +
    + Профиль +
    + + + + +
    +
    + {% endfor %} +
    + + + + + + + \ No newline at end of file diff --git a/dogs_around/webapp_dogs/templates/index.html b/dogs_around/webapp_dogs/templates/index.html new file mode 100644 index 0000000..b461e89 --- /dev/null +++ b/dogs_around/webapp_dogs/templates/index.html @@ -0,0 +1,151 @@ + + + + + + + {{page_title}} + + + + + + {% with messages = get_flashed_messages() %} + {% if messages %} +
      + {% for message in messages %} +
    • {{ message }}
    • + {% endfor %} +
    + {% endif %} + {% endwith %} +
    + Собакруг +
    +
    + {{page_title}} +
    + +

    Сегодня в круге:

    +
    + {% for dog in dogs %} +
    +
    + {{ dog.name_dog }} +
    +
    +

    Кличка: {{ dog.name_dog }}

    +

    Порода: {{ dog.breed_dog }}

    +

    Возраст: {{ dog.age_dog }}

    +
    +
    + {% endfor %} +
    + + + + + + + \ No newline at end of file diff --git a/dogs_around/webapp_dogs/templates/login.css b/dogs_around/webapp_dogs/templates/login.css new file mode 100644 index 0000000..67664a8 --- /dev/null +++ b/dogs_around/webapp_dogs/templates/login.css @@ -0,0 +1,6 @@ +.form-container { + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + } \ No newline at end of file diff --git a/dogs_around/webapp_dogs/templates/login.html b/dogs_around/webapp_dogs/templates/login.html new file mode 100644 index 0000000..94d4f7e --- /dev/null +++ b/dogs_around/webapp_dogs/templates/login.html @@ -0,0 +1,50 @@ + + + + + + + {{ page_title }} + + + + + + + + + {% with messages = get_flashed_messages() %} + {% if messages %} +
      + {% for message in messages %} +
    • {{ message }}
    • + {% endfor %} +
    + {% endif %} + {% endwith %} +
    +
    + {{ form.hidden_tag() }} +
    + {{ form.email.label }} {{ form.email(size=20, class_='form-control', style='width:30%;') }} + {% for error in form.email.errors %} +
    {{ error }}
    + {% endfor %} +
    +
    + {{ form.password.label }} {{ form.password(size=20, class_='form-control', style='width:30%;') }} + {% for error in form.password.errors %} +
    {{ error }}
    + {% endfor %} +
    + {{ form.submit }} + +
    + + + + + + + + diff --git a/dogs_around/webapp_dogs/templates/profile.html b/dogs_around/webapp_dogs/templates/profile.html new file mode 100644 index 0000000..f598f7e --- /dev/null +++ b/dogs_around/webapp_dogs/templates/profile.html @@ -0,0 +1,46 @@ + + + + + + + + + + + {{ page_title }} + + +
    +

    {{ page_title }}

    + +
    +
    + {% with messages = get_flashed_messages() %} + {% if messages %} + + {% endif %} + {% endwith %} +
    + {% if user %} +
    +

    Имя: {{ user.first_name }}

    +

    Фамилия: {{ user.last_name }}

    +

    Ник: {{ user.user_name }}

    +

    Почта: {{ user.email }}

    +
    + {% endif %} +
    +
    +
    +
    + + + + + + + + + + + + diff --git a/dogs_around/webapp_dogs/templates/registration.html b/dogs_around/webapp_dogs/templates/registration.html new file mode 100644 index 0000000..43016d2 --- /dev/null +++ b/dogs_around/webapp_dogs/templates/registration.html @@ -0,0 +1,67 @@ + + + + + + + {{ page_title }} + + + + + + + + +
    +

    Registration

    +
    + {{ form.csrf_token }} +
    + {{ form.first_name.label }} {{ form.first_name(size=20, class_='form-control', style='width:50%;') }} + {% for error in form.first_name.errors %} +
    {{ error }}
    + {% endfor %} +
    +
    + {{ form.last_name.label }} {{ form.last_name(size=20, class_='form-control',style='width:50%;') }} + {% for error in form.last_name.errors %} +
    {{ error }}
    + {% endfor %} +
    +
    + {{ form.user_name.label }} {{ form.user_name(size=20, class_='form-control', style='width:50%;') }} + {% for error in form.user_name.errors %} +
    {{ error }}
    + {% endfor %} +
    +
    + {{ form.email.label }} {{ form.email(size=20, class_='form-control', style='width:50%;') }} + {% for error in form.email.errors %} +
    {{ error }}
    + {% endfor %} +
    +
    + {{ form.password.label }} {{ form.password(size=20, class_='form-control', style='width:50%;') }} + {% for error in form.password.errors %} +
    {{ error }}
    + {% endfor %} +
    +
    + {{ form.confirm_password.label }} {{ form.confirm_password(size=20, class_='form-control', style='width:50%;') }} + {% for error in form.confirm_password.errors %} +
    {{ error }}
    + {% endfor %} +
    +
    + {{ form.submit(class_='btn btn-primary') }} +
    + +
    + + + + + + + diff --git a/dogs_around/webapp_dogs/templates/user_dogs.html b/dogs_around/webapp_dogs/templates/user_dogs.html new file mode 100644 index 0000000..cbc2b4e --- /dev/null +++ b/dogs_around/webapp_dogs/templates/user_dogs.html @@ -0,0 +1,210 @@ + + + + + + + {{ page_title }} + + + + + + + + + + {% with messages = get_flashed_messages() %} + {% if messages %} +
      + {% for message in messages %} +
    • {{ message }}
    • + {% endfor %} +
    + {% endif %} +{% endwith %} +
    +

    Мои собачки:

    +
    + {% for dog in dogs %} +
    +
    + {{ dog.name_dog }} +
    +
    +

    Кличка: {{ dog.name_dog }}

    +

    Порода: {{ dog.breed_dog }}

    +

    Возраст: {{ dog.age_dog }}

    +
    + +
    + {% endfor %} +
    +
    +
    +
    +

    Друзья собачек:

    +
    + {% for friend in friends %} +
    +
    +

    Кличка: {{ friend.name_dog }}

    +

    Порода: {{ friend.breed_dog }}

    +

    Возраст: {{ friend.age_dog }}

    +
    + +
    + {% endfor %} +
    +
    +
    + +
    +

    Заявки в друзья:

    +
    + {% for request in friend_requests %} +
    +
    +

    Кличка: {{ request.name_dog }}

    +

    Порода: {{ request.breed_dog }}

    +

    Возраст: {{ request.age_dog }}

    +
    + +
    + {% endfor %} +
    +
    + + + + + + + + \ No newline at end of file diff --git a/dogs_around/webapp_dogs/utils_dog.py b/dogs_around/webapp_dogs/utils_dog.py new file mode 100644 index 0000000..0648637 --- /dev/null +++ b/dogs_around/webapp_dogs/utils_dog.py @@ -0,0 +1,63 @@ +from flask import Flask +from webapp_dogs.model import db, User, Dog +from webapp_dogs.config import SQLALCHEMY_DATABASE_URI +from sqlalchemy.exc import SQLAlchemyError +from flask_sqlalchemy import SQLAlchemy +import hashlib +import uuid + +def generate_id_user(): + uuid_val = uuid.uuid4().bytes + # Получаем хэш от UUID с использованием sha256 + hash_object = hashlib.sha256(uuid_val) + hex_dig = hash_object.hexdigest() + return hex_dig + +def generate_id_dog(name_dog, age_dog, breed_dog, response_date, city_dog): + input_str = f"{name_dog}-{age_dog}-{breed_dog}-{response_date}-{city_dog}" + # Получаем хэш от строки с использованием sha256 + hash_object = hashlib.sha256(input_str.encode()) + hex_dig = hash_object.hexdigest() + return hex_dig + +def get_or_create_user(id_user, first_name, last_name, user_name, email, hashed_password) -> None: + app = Flask(__name__) + app.config['SQLALCHEMY_DATABASE_URI'] = SQLALCHEMY_DATABASE_URI + try: + with app.app_context(): + try: + db.init_app(app) + user_exists = User.query.filter(User.email == email).first() + if not user_exists: + new_user = User(id_user=id_user, first_name=first_name, last_name=last_name, user_name=user_name, email= email, password=hashed_password) + db.session.add(new_user) + db.session.commit() + return f'Пользователь с почтой {email} зарегистрирован.' + else: + return f'Пользователь с почтой {email} уже зарегистрирован.' + except SQLAlchemyError as sq: + print(f'Ошибка sqlalchemy записи пользователя tg в get_or_create_user в бд: {sq}') + except Exception as ex1: + print(f'Ошибка в функции обработки данных пользователя: {ex1}') + +def get_or_create_dog(id_dog, name_dog, age_dog, breed_dog, response_date, + city_dog, foto_dog, voice_dog, username) -> None: + app = Flask(__name__) + app.config['SQLALCHEMY_DATABASE_URI'] = SQLALCHEMY_DATABASE_URI + try: + with app.app_context(): + try: + db.init_app(app) + user = User.query.filter_by(email=username).first() + dog_exists = Dog.query.filter(Dog.id_dog == id_dog).first() + if not dog_exists: + new_dog = Dog(id_dog=id_dog,name_dog=name_dog, age_dog=age_dog, breed_dog=breed_dog, response_date=response_date, + city_dog=city_dog, foto_dog=foto_dog, voice_dog=voice_dog) + user.dogs.append(new_dog) + db.session.add(new_dog) + db.session.commit() + return dog_exists + except SQLAlchemyError as sq: + print(f'Ошибка sqlalchemy записи пользователя tg в get_or_create_user в бд: {sq}') + except Exception as ex1: + print(f'Ошибка функции обработки данных собаки: {ex1}') \ No newline at end of file diff --git a/web_flask/.gitignore b/web_flask_track/.gitignore similarity index 100% rename from web_flask/.gitignore rename to web_flask_track/.gitignore diff --git a/web_flask/create_admin.py b/web_flask_track/create_admin.py similarity index 100% rename from web_flask/create_admin.py rename to web_flask_track/create_admin.py diff --git a/web_flask/create_db.py b/web_flask_track/create_db.py similarity index 100% rename from web_flask/create_db.py rename to web_flask_track/create_db.py diff --git a/web_flask/get_all_news.py b/web_flask_track/get_all_news.py similarity index 100% rename from web_flask/get_all_news.py rename to web_flask_track/get_all_news.py diff --git a/web_flask/migrations/README b/web_flask_track/migrations/README similarity index 100% rename from web_flask/migrations/README rename to web_flask_track/migrations/README diff --git a/web_flask/migrations/alembic.ini b/web_flask_track/migrations/alembic.ini similarity index 100% rename from web_flask/migrations/alembic.ini rename to web_flask_track/migrations/alembic.ini diff --git a/web_flask/migrations/env.py b/web_flask_track/migrations/env.py similarity index 100% rename from web_flask/migrations/env.py rename to web_flask_track/migrations/env.py diff --git a/web_flask/migrations/script.py.mako b/web_flask_track/migrations/script.py.mako similarity index 100% rename from web_flask/migrations/script.py.mako rename to web_flask_track/migrations/script.py.mako diff --git a/web_flask/migrations/versions/92ca0a22999f_added_email_to_user.py b/web_flask_track/migrations/versions/92ca0a22999f_added_email_to_user.py similarity index 100% rename from web_flask/migrations/versions/92ca0a22999f_added_email_to_user.py rename to web_flask_track/migrations/versions/92ca0a22999f_added_email_to_user.py diff --git a/web_flask/migrations/versions/e39062663631_users_and_news_tables.py b/web_flask_track/migrations/versions/e39062663631_users_and_news_tables.py similarity index 100% rename from web_flask/migrations/versions/e39062663631_users_and_news_tables.py rename to web_flask_track/migrations/versions/e39062663631_users_and_news_tables.py diff --git a/web_flask/python-org-news.html b/web_flask_track/python-org-news.html similarity index 100% rename from web_flask/python-org-news.html rename to web_flask_track/python-org-news.html diff --git a/web_flask/requirements.txt b/web_flask_track/requirements.txt similarity index 100% rename from web_flask/requirements.txt rename to web_flask_track/requirements.txt diff --git a/web_flask/run.sh b/web_flask_track/run.sh similarity index 100% rename from web_flask/run.sh rename to web_flask_track/run.sh diff --git a/web_flask/webapp/__init__.py b/web_flask_track/webapp/__init__.py similarity index 100% rename from web_flask/webapp/__init__.py rename to web_flask_track/webapp/__init__.py diff --git a/web_flask/webapp/bs_ex.py b/web_flask_track/webapp/bs_ex.py similarity index 100% rename from web_flask/webapp/bs_ex.py rename to web_flask_track/webapp/bs_ex.py diff --git a/web_flask/webapp/forms.py b/web_flask_track/webapp/forms.py similarity index 100% rename from web_flask/webapp/forms.py rename to web_flask_track/webapp/forms.py diff --git a/web_flask/webapp/model.py b/web_flask_track/webapp/model.py similarity index 100% rename from web_flask/webapp/model.py rename to web_flask_track/webapp/model.py diff --git a/web_flask/webapp/templates/index.html b/web_flask_track/webapp/templates/index.html similarity index 100% rename from web_flask/webapp/templates/index.html rename to web_flask_track/webapp/templates/index.html diff --git a/web_flask/webapp/templates/login.html b/web_flask_track/webapp/templates/login.html similarity index 100% rename from web_flask/webapp/templates/login.html rename to web_flask_track/webapp/templates/login.html diff --git a/web_flask/webapp/weather.py b/web_flask_track/webapp/weather.py similarity index 100% rename from web_flask/webapp/weather.py rename to web_flask_track/webapp/weather.py