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": [
" "
@@ -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+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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",
"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
-[](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 %}
+
+ {% for message in messages %}
+ {{ message }}
+ {% endfor %}
+
+ {% endif %}
+ {% endwith %}
+
+
Отзыв:
+
+
+ {% 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.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.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 %}
+
+
+
+
+
+
+
+
+
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 %}
+
+ {% for message in messages %}
+ {{ message }}
+ {% endfor %}
+
+ {% 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 }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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