{ "cells": [ { "cell_type": "markdown", "id": "15084c07-206f-431c-b15f-6c8a7cff53a6", "metadata": {}, "source": [ "# Pandas: Advanced Data Manipulation and Aggregation\n", "\n", "![](https://ds1002-resources.s3.amazonaws.com/images/workflow.png)\n", "\n", "In this lesson, you will learn advanced data manipulation techniques using Pandas. Specifically, we will cover:\n", " \n", "- Combining dataframes\n", "- Data aggregation\n", "- Data reshaping" ] }, { "cell_type": "code", "execution_count": 1, "id": "ca3e52c1-205a-4b79-a122-ca6de7694f08", "metadata": {}, "outputs": [], "source": [ "# Load dependencies (NumPy and Pandas) \n", "import pandas as pd\n", "import numpy as np" ] }, { "cell_type": "code", "execution_count": 2, "id": "8d5c18d4-14ae-4298-bfe5-f36d6ebbfa7d", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
sepal_lengthsepal_widthpetal_lengthpetal_widthspecies
05.13.51.40.2setosa
14.93.01.40.2setosa
24.73.21.30.2setosa
34.63.11.50.2setosa
45.03.61.40.2setosa
..................
1456.73.05.22.3virginica
1466.32.55.01.9virginica
1476.53.05.22.0virginica
1486.23.45.42.3virginica
1495.93.05.11.8virginica
\n", "

150 rows × 5 columns

\n", "
" ], "text/plain": [ " sepal_length sepal_width petal_length petal_width species\n", "0 5.1 3.5 1.4 0.2 setosa\n", "1 4.9 3.0 1.4 0.2 setosa\n", "2 4.7 3.2 1.3 0.2 setosa\n", "3 4.6 3.1 1.5 0.2 setosa\n", "4 5.0 3.6 1.4 0.2 setosa\n", ".. ... ... ... ... ...\n", "145 6.7 3.0 5.2 2.3 virginica\n", "146 6.3 2.5 5.0 1.9 virginica\n", "147 6.5 3.0 5.2 2.0 virginica\n", "148 6.2 3.4 5.4 2.3 virginica\n", "149 5.9 3.0 5.1 1.8 virginica\n", "\n", "[150 rows x 5 columns]" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# We will keep using the Iris dataset for this tutorial\n", "iris_df = pd.read_csv(\"https://raw.githubusercontent.com/mwaskom/seaborn-data/refs/heads/master/iris.csv\")\n", "iris_df" ] }, { "cell_type": "markdown", "id": "383c6fe5-50d7-4b20-b761-cbe3db8c47fe", "metadata": {}, "source": [ "## Combine dataframes\n", "\n", "### Concate: `pd.concat()` \n", "\n", "It allows you to concatenate pandas objects along a particular axis. See [documentation](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.concat.html) for further details." ] }, { "cell_type": "markdown", "id": "23e8b17e-adab-4594-a8ad-2b72ad72eae0", "metadata": {}, "source": [ "- **Concat rows**\n", "\n", "Here we would be combining two datasets with the same features (columns) but different observations.\n", "\n", "![](https://pandas.pydata.org/docs/_images/merging_concat_basic.png)" ] }, { "cell_type": "code", "execution_count": 3, "id": "869e9f26-9576-4128-a6ab-f4bdb13cd8ed", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " a b c d\n", "0 1.762846 0.599143 -0.060141 0.941592\n", "1 1.287035 1.796988 0.584070 -0.263635\n", "2 -0.442917 1.979003 0.450980 0.871701\n", "---------------------------------------------\n", " a b c d\n", "0 0.648084 -0.626128 -1.197426 -0.084410\n", "1 -0.419176 -0.396442 0.455378 0.800460\n", "2 1.953168 0.256243 0.468648 0.110187\n", "---------------------------------------------\n", " a b c d\n", "0 1.762846 0.599143 -0.060141 0.941592\n", "1 1.287035 1.796988 0.584070 -0.263635\n", "2 -0.442917 1.979003 0.450980 0.871701\n", "0 0.648084 -0.626128 -1.197426 -0.084410\n", "1 -0.419176 -0.396442 0.455378 0.800460\n", "2 1.953168 0.256243 0.468648 0.110187\n" ] } ], "source": [ "# Create two dfs and vertically stack them.\n", "df1 = pd.DataFrame(np.random.randn(3, 4), columns=[\"a\", \"b\", \"c\", \"d\"])\n", "df2 = pd.DataFrame(np.random.randn(3, 4), columns=[\"a\", \"b\", \"c\", \"d\"])\n", "\n", "print(df1)\n", "print('-'*45)\n", "print(df2)\n", "\n", "df3 = pd.concat([df1, df2], axis=0)\n", "\n", "print('-'*45)\n", "print(df3)" ] }, { "cell_type": "markdown", "id": "dff68262-90ba-4e21-9107-1695388d51f9", "metadata": {}, "source": [ "- **Concat columns**.\n", "\n", "Here our datasets have the same IDs, for example, subjects or time points, but different measures (columns).\n", "\n", "\n", "![](https://pandas.pydata.org/docs/_images/merging_concat_axis1_join_axes.png)" ] }, { "cell_type": "code", "execution_count": 4, "id": "c6a7e550-9972-47ea-b271-32a490dcb5ff", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
abcdxyz
0-0.903242-2.3779920.023772-0.440048-3.0217920.4370091.067440
1-0.6644850.054503-3.138605-1.712360-0.4829350.1187880.411506
2-0.7828770.5530140.5434120.4536170.4820050.547735-2.867207
\n", "
" ], "text/plain": [ " a b c d x y z\n", "0 -0.903242 -2.377992 0.023772 -0.440048 -3.021792 0.437009 1.067440\n", "1 -0.664485 0.054503 -3.138605 -1.712360 -0.482935 0.118788 0.411506\n", "2 -0.782877 0.553014 0.543412 0.453617 0.482005 0.547735 -2.867207" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Create two dfs and vertically stack them.\n", "df1 = pd.DataFrame(np.random.randn(3, 4), columns=[\"a\", \"b\", \"c\", \"d\"])\n", "df2 = pd.DataFrame(np.random.randn(3, 3), columns=[\"x\", \"y\", \"z\"])\n", "\n", "df4 = pd.concat([df1,df2], axis = 1)\n", "\n", "df4" ] }, { "attachments": {}, "cell_type": "markdown", "id": "f942b10c-0cde-4adb-a1e2-195144c6e169", "metadata": {}, "source": [ "### Merge: `pd.merge()`\n", "\n", "SQL-style joining of tables (dataframes)\n", "\n", "Important parameters include:\n", "\n", "- `how` : type of merge {'left', 'right', 'outer', 'inner', 'cross'}, default ‘inner’\n", "- `on` : names to join on. Normally it indicates the name of the column for matching up the observations.\n", " \n", "See [documentation](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.merge.html) for further details.\n", "\n", "\n", "![](https://pandas.pydata.org/docs/_images/merging_merge_on_key.png)\n" ] }, { "cell_type": "markdown", "id": "df302e38-6caf-40dd-a2b9-ec2efa00917a", "metadata": {}, "source": [ "
Personal note: This is probably one of the most useful functions in Pandas. I use it almost in any project where I have to combine different datasets (very common!)
" ] }, { "cell_type": "markdown", "id": "dfbdb7ee-aafd-4ff8-bc09-6da066178f15", "metadata": {}, "source": [ "Look at the follow example:" ] }, { "cell_type": "code", "execution_count": 5, "id": "77899cbc-dc68-411e-8ff2-69d2db87c9ba", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "---left\n", " key lval\n", "0 jamie 15\n", "1 bill 22\n", "\n", "---right\n", " key rval\n", "0 jamie 4\n", "1 bill 5\n", "2 asher 8\n", "\n", "---joined\n" ] }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
keylvalrval
0jamie15.04
1bill22.05
2asherNaN8
\n", "
" ], "text/plain": [ " key lval rval\n", "0 jamie 15.0 4\n", "1 bill 22.0 5\n", "2 asher NaN 8" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Create two tables, `left` and `right`.\n", "left = pd.DataFrame({\"key\": [\"jamie\", \"bill\"], \"lval\": [15, 22]})\n", "right = pd.DataFrame({\"key\": [\"jamie\", \"bill\", \"asher\"], \"rval\": [4, 5, 8]})\n", "\n", "# Right join them on `key`, which means including all records from table on right.\n", "joined = pd.merge(left, right, on=\"key\", how=\"right\")\n", "\n", "print('---left')\n", "print(left)\n", "print('\\n---right')\n", "print(right)\n", "print('\\n---joined')\n", "joined" ] }, { "cell_type": "code", "execution_count": 6, "id": "68600722-54a7-464f-a364-61a8baa72207", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
keylvalrval
0jamie154
1bill225
\n", "
" ], "text/plain": [ " key lval rval\n", "0 jamie 15 4\n", "1 bill 22 5" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Compare to left join\n", "pd.merge(left, right, on=\"key\")" ] }, { "cell_type": "markdown", "id": "ba70abc3-694d-4d14-928b-a78f69e1b8ea", "metadata": {}, "source": [ "### Join: `join()`\n", "\n", "An SQL-like joiner, but this one takes advantage of indexes.\n", "\n", "Give our dataframes indexes and distinctive columns names.\n", "\n", "See [documentation](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.join.html) for further details.\n", "\n", "\n", "![](https://pandas.pydata.org/docs/_images/merging_join.png)\n" ] }, { "cell_type": "code", "execution_count": 7, "id": "425e39df-46f9-4e41-bdaf-d1081595f7e3", "metadata": {}, "outputs": [], "source": [ "left = pd.DataFrame(\n", " {\"A\": [\"A0\", \"A1\", \"A2\"], \"B\": [\"B0\", \"B1\", \"B2\"]}, index=[\"K0\", \"K1\", \"K2\"])\n", " \n", "right = pd.DataFrame(\n", " {\"C\": [\"C0\", \"C2\", \"C3\"], \"D\": [\"D0\", \"D2\", \"D3\"]}, index=[\"K0\", \"K2\", \"K3\"])" ] }, { "cell_type": "code", "execution_count": 8, "id": "69dd6269-0ce6-4233-9de3-bee6b694d064", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
CDAB
K0C0D0A0B0
K2C2D2A2B2
K3C3D3NaNNaN
\n", "
" ], "text/plain": [ " C D A B\n", "K0 C0 D0 A0 B0\n", "K2 C2 D2 A2 B2\n", "K3 C3 D3 NaN NaN" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "right.join(left)" ] }, { "cell_type": "code", "execution_count": 9, "id": "6633d37e-b93c-4a2c-9706-c0f8c85899dd", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
ABCD
K0A0B0C0D0
K1A1B1NaNNaN
K2A2B2C2D2
\n", "
" ], "text/plain": [ " A B C D\n", "K0 A0 B0 C0 D0\n", "K1 A1 B1 NaN NaN\n", "K2 A2 B2 C2 D2" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "left.join(right)" ] }, { "cell_type": "markdown", "id": "0eb6b71a-25f1-44f1-a4af-ce6377732756", "metadata": {}, "source": [ "### Summary\n", "\n", "* Use **concat** to combine based on shared indexes or columns.\n", "* Use **merge** if you want to combine datasets given a column (e.g. subject records).\n", "* Use **join** if you have shared indexes." ] }, { "cell_type": "markdown", "id": "08dd64e7-5ef2-43cf-9198-ff63dc38400c", "metadata": {}, "source": [ "## Data Aggregation\n", "\n", "Involves one or more of:\n", "\n", "- Splitting the data into groups\n", "- Applying a function to each group\n", "- Combining results" ] }, { "cell_type": "markdown", "id": "cf1a6bc2-705f-44f9-8497-8a5fc53b948e", "metadata": {}, "source": [ "### `groupby()` method\n", "\n", "It allows you to compute summary statistics (e.g., sum, mean) on groups of data, which is essential for summarizing and exploring grouped data.\n", "\n", "- **Basic case**: `dataframe.groupby(\"column_name\").aggregation method`" ] }, { "cell_type": "code", "execution_count": 10, "id": "9fff6ac6-bd68-46af-90d2-cb994becb5f8", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
sepal_lengthsepal_widthpetal_lengthpetal_width
species
setosa5.0063.4281.4620.246
versicolor5.9362.7704.2601.326
virginica6.5882.9745.5522.026
\n", "
" ], "text/plain": [ " sepal_length sepal_width petal_length petal_width\n", "species \n", "setosa 5.006 3.428 1.462 0.246\n", "versicolor 5.936 2.770 4.260 1.326\n", "virginica 6.588 2.974 5.552 2.026" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Dataframe --> group by species --> aggregate through the mean\n", "iris_df.groupby(\"species\").mean()" ] }, { "cell_type": "code", "execution_count": 11, "id": "87c64b9e-8b60-454b-a190-0b52a908c827", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
sepal_lengthsepal_widthpetal_lengthpetal_width
species
setosa4.32.31.00.1
versicolor4.92.03.01.0
virginica4.92.24.51.4
\n", "
" ], "text/plain": [ " sepal_length sepal_width petal_length petal_width\n", "species \n", "setosa 4.3 2.3 1.0 0.1\n", "versicolor 4.9 2.0 3.0 1.0\n", "virginica 4.9 2.2 4.5 1.4" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Dataframe --> group by species --> aggregate through the minimum\n", "iris_df.groupby(\"species\").min()" ] }, { "cell_type": "markdown", "id": "ad0963de-407c-448f-a6b1-cda13a75f876", "metadata": {}, "source": [ "You can find a full list of aggregation methods here: https://pandas.pydata.org/pandas-docs/stable/user_guide/groupby.html#built-in-aggregation-methods" ] }, { "cell_type": "markdown", "id": "64f886ec-e3aa-46b8-b196-b215d29fb98c", "metadata": {}, "source": [ "- **More than one aggregation method**: `agg()` method on the grouped data frame\n", "\n", "See https://pandas.pydata.org/pandas-docs/stable/user_guide/groupby.html#the-aggregate-method" ] }, { "cell_type": "code", "execution_count": 13, "id": "ce4ffb3a-0e71-4a73-a9af-423479c9d863", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
sepal_lengthsepal_widthpetal_lengthpetal_width
minmeanmaxcountminmeanmaxcountminmeanmaxcountminmeanmaxcount
species
setosa4.35.0065.8502.33.4284.4501.01.4621.9500.10.2460.650
versicolor4.95.9367.0502.02.7703.4503.04.2605.1501.01.3261.850
virginica4.96.5887.9502.22.9743.8504.55.5526.9501.42.0262.550
\n", "
" ], "text/plain": [ " sepal_length sepal_width \\\n", " min mean max count min mean max count \n", "species \n", "setosa 4.3 5.006 5.8 50 2.3 3.428 4.4 50 \n", "versicolor 4.9 5.936 7.0 50 2.0 2.770 3.4 50 \n", "virginica 4.9 6.588 7.9 50 2.2 2.974 3.8 50 \n", "\n", " petal_length petal_width \n", " min mean max count min mean max count \n", "species \n", "setosa 1.0 1.462 1.9 50 0.1 0.246 0.6 50 \n", "versicolor 3.0 4.260 5.1 50 1.0 1.326 1.8 50 \n", "virginica 4.5 5.552 6.9 50 1.4 2.026 2.5 50 " ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "iris_df.groupby(\"species\").agg(['min', 'mean', \"max\", \"count\"])" ] }, { "cell_type": "markdown", "id": "4c7a9776-70b0-4fdd-8983-7951e46f79f8", "metadata": {}, "source": [ "- **Multiple columns**" ] }, { "cell_type": "code", "execution_count": 14, "id": "44f936fc-7d11-41a3-8d08-387366f907f0", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
sepal_lengthsepal_widthpetal_lengthpetal_width
speciespetal_width_bin
setosalow5.00603.42801.46200.246
versicolorhigh6.06752.86254.42251.400
low5.41002.40003.61001.030
virginicahigh6.58802.97405.55202.026
\n", "
" ], "text/plain": [ " sepal_length sepal_width petal_length \\\n", "species petal_width_bin \n", "setosa low 5.0060 3.4280 1.4620 \n", "versicolor high 6.0675 2.8625 4.4225 \n", " low 5.4100 2.4000 3.6100 \n", "virginica high 6.5880 2.9740 5.5520 \n", "\n", " petal_width \n", "species petal_width_bin \n", "setosa low 0.246 \n", "versicolor high 1.400 \n", " low 1.030 \n", "virginica high 2.026 " ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "iris_df.loc[iris_df[\"petal_width\"] >= iris_df[\"petal_width\"].mean(), \"petal_width_bin\"] = \"high\"\n", "iris_df.loc[iris_df[\"petal_width\"] < iris_df[\"petal_width\"].mean(), \"petal_width_bin\"] = \"low\"\n", "\n", "iris_df.groupby([\"species\", \"petal_width_bin\"]).mean()" ] }, { "cell_type": "markdown", "id": "48606b8d-9c18-48ae-b4c6-c54b93d11591", "metadata": {}, "source": [ "- **Multiple columns and multiple aggregation methods**" ] }, { "cell_type": "code", "execution_count": 15, "id": "a0401d66-186a-4d51-903f-63ade4bef736", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
sepal_lengthsepal_widthpetal_lengthpetal_width
minmeanmaxcountminmeanmaxcountminmeanmaxcountminmeanmaxcount
speciespetal_width_bin
setosalow4.35.00605.8502.33.42804.4501.01.46201.9500.10.2460.650
versicolorhigh5.26.06757.0402.22.86253.4403.64.42255.1401.21.4001.840
low4.95.41006.0102.02.40002.7103.03.61004.1101.01.0301.110
virginicahigh4.96.58807.9502.22.97403.8504.55.55206.9501.42.0262.550
\n", "
" ], "text/plain": [ " sepal_length sepal_width \\\n", " min mean max count min \n", "species petal_width_bin \n", "setosa low 4.3 5.0060 5.8 50 2.3 \n", "versicolor high 5.2 6.0675 7.0 40 2.2 \n", " low 4.9 5.4100 6.0 10 2.0 \n", "virginica high 4.9 6.5880 7.9 50 2.2 \n", "\n", " petal_length \\\n", " mean max count min mean max count \n", "species petal_width_bin \n", "setosa low 3.4280 4.4 50 1.0 1.4620 1.9 50 \n", "versicolor high 2.8625 3.4 40 3.6 4.4225 5.1 40 \n", " low 2.4000 2.7 10 3.0 3.6100 4.1 10 \n", "virginica high 2.9740 3.8 50 4.5 5.5520 6.9 50 \n", "\n", " petal_width \n", " min mean max count \n", "species petal_width_bin \n", "setosa low 0.1 0.246 0.6 50 \n", "versicolor high 1.2 1.400 1.8 40 \n", " low 1.0 1.030 1.1 10 \n", "virginica high 1.4 2.026 2.5 50 " ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "iris_df.groupby([\"species\", \"petal_width_bin\"]).agg(['min', 'mean', \"max\", \"count\"])" ] }, { "cell_type": "markdown", "id": "c1f0f6a4-955a-45c0-bd8d-96948b8f04d4", "metadata": {}, "source": [ "### `pd.pivot_table()` function\n", "\n", "This function allows you to apply a function `aggfunc` to selected values grouped by columns. See [documentation](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.pivot_table.html) for further details." ] }, { "cell_type": "markdown", "id": "48857f21-842c-4655-887f-2cb6bf441b19", "metadata": {}, "source": [ "Compute mean sepal length for each species:" ] }, { "cell_type": "code", "execution_count": 16, "id": "779c5fbd-fce1-4a41-8f34-1c0642feb70a", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
speciessetosaversicolorvirginica
sepal_length5.0065.9366.588
\n", "
" ], "text/plain": [ "species setosa versicolor virginica\n", "sepal_length 5.006 5.936 6.588" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pd.pivot_table(iris_df, values=\"sepal_length\", columns=[\"species\"], aggfunc = np.mean)" ] }, { "cell_type": "code", "execution_count": 17, "id": "4e56b79f-9506-4c1e-aaea-abdba1366811", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
speciessetosaversicolorvirginica
sepal_length5.0065.9366.588
\n", "
" ], "text/plain": [ "species setosa versicolor virginica\n", "sepal_length 5.006 5.936 6.588" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Similar to:\n", "iris_df.groupby(\"species\")[[\"sepal_length\"]].mean().T" ] }, { "cell_type": "markdown", "id": "0b711382-80e5-43ac-8d51-a886cc6e2bd0", "metadata": {}, "source": [ "## Reshaping Data" ] }, { "cell_type": "markdown", "id": "607a89dd-fa19-45a8-be1d-40d856923c91", "metadata": {}, "source": [ "### `pd.melt()`\n", "\n", "It allows you to convert a dataframe to long format. \n", "\n", "It is useful to convert a dataframe into a format where one or more columns are identifier variables (id_vars), while all other columns, considered measured variables (value_vars).\n", "\n", "
Personal note: This is probably another useful function in Pandas, which I also use in almost all projects, particularly when I have to plot things (see below).
" ] }, { "cell_type": "markdown", "id": "ac94510c-9444-4703-8d2f-b9f5639d9071", "metadata": {}, "source": [ "![](https://pandas.pydata.org/docs/_images/reshaping_melt.png)" ] }, { "cell_type": "markdown", "id": "894c8a5a-f7eb-42b4-9799-aa1dc74973c8", "metadata": {}, "source": [ "From our original iris dataframe, say we want our `species` to be identifier variables, while the rest be different measures. We can do the following:" ] }, { "cell_type": "code", "execution_count": 18, "id": "980be8fc-80d5-40c7-bdfa-a3957a741c3c", "metadata": {}, "outputs": [], "source": [ "# This just drops the previously binarized petal_width column\n", "iris_df = iris_df.drop(columns=\"petal_width_bin\")" ] }, { "cell_type": "code", "execution_count": 19, "id": "a17360a7-4db3-46fd-b3cd-1471b0167265", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
speciesvariablevalue
0setosasepal_length5.1
1setosasepal_length4.9
2setosasepal_length4.7
3setosasepal_length4.6
4setosasepal_length5.0
............
595virginicapetal_width2.3
596virginicapetal_width1.9
597virginicapetal_width2.0
598virginicapetal_width2.3
599virginicapetal_width1.8
\n", "

600 rows × 3 columns

\n", "
" ], "text/plain": [ " species variable value\n", "0 setosa sepal_length 5.1\n", "1 setosa sepal_length 4.9\n", "2 setosa sepal_length 4.7\n", "3 setosa sepal_length 4.6\n", "4 setosa sepal_length 5.0\n", ".. ... ... ...\n", "595 virginica petal_width 2.3\n", "596 virginica petal_width 1.9\n", "597 virginica petal_width 2.0\n", "598 virginica petal_width 2.3\n", "599 virginica petal_width 1.8\n", "\n", "[600 rows x 3 columns]" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "iris_melted = pd.melt(iris_df, id_vars=\"species\")\n", "iris_melted" ] }, { "cell_type": "markdown", "id": "15b58883-c42e-4995-8e50-bcb81f48db4f", "metadata": {}, "source": [ "This is very useful if we want to plot both measures together, stratified by our identifed variable:" ] }, { "cell_type": "code", "execution_count": 20, "id": "fd401f63-5c07-449c-b4f2-1173859a58f9", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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6NHKqj7qAOS69vaOXT8KlZuba1Ka/mH16tVDpUpsEKuIeO0rtt9//x3Z2dmFrvP29oa3t7eNIyJb4bUvHSYqREREpFjs+iEiIiLFYqJCREREisVEhYiIiBSLiQoREREpFhMVIpLd5cuXIUkSjh8/XuJ9oqOj0bt372K36dKlC0aMGFGm2IhIXrzHkYhk5+fnh4yMDFSvXl3uUIhIYZioEJGscnJy4OzsDB8fH7lDISIFYtcPEZXY4sWLUbdu3QIzZj/77LOIiorChQsX0KtXL9SqVQtubm4ICgrCzp07TbatV68epk2bhujoaHh6emLQoEEFun70ej0GDhyI+vXrw8XFBY0bN8b8+fMLjWny5MmoWbMmPDw88OabbyInJ6fI+HNycjB27FjUrVsXVapUQYcOHbBnz54yvSdEVL6YqBBRifXt2xfXr1/H7t27jcsyMzORmJiIiIgI3LlzB08//TR27tyJY8eOITw8HD179ixQ9n/WrFlo0aIFjhw5gvfff7/AeQwGA3x9ffH111/j1KlTiImJwf/93//h66+/Ntlu165dOH36NHbv3o2vvvoKGzZswOTJk4uM/9VXX0VSUhLWrFmDEydOoG/fvujevTvOnTtXxneGiMqNnFM3E5H9efbZZ8Vrr71mfL548WLh4+MjcnNzC92+WbNmYsGCBcbn/v7+onfv3ibbXLp0qcB09A8bMmSIeP75543Po6KihLe3t7h7965x2aJFi4Sbm5vQ6/VCCNPp68+fPy8kSRK///67yXG7du0qxo8fX/yLJiLZsEWFiCwSERGBdevWQafTAQASEhLQv39/qFQq3L17F2PHjkWzZs3g5eUFNzc3nDlzpkCLSmBgoNnzfPrppwgMDESNGjXg5uaGpUuXFjhO69at4erqanweHByMO3fuID09vcDxjh49CiEEGjVqZDIZ3N69e3HhwoXSvBVEZAMcTEtEFunZsycMBgO2bt2KoKAg7N+/H3PmzAEAjBkzBomJiZg9ezYaNGgAFxcXvPDCCwXGjVSpUqXYc3z99dcYOXIk4uPjERwcDHd3d8yaNQuHDx8uUYySJBVYZjAYoFKpcOTIEahUKpN1bm5uJTouEdkeExUisoiLiwv69OmDhIQEnD9/Ho0aNUK7du0AAPv370d0dDSee+45AMCdO3eKnQm6KPv370fHjh0xZMgQ47LCWj1++eUXZGdnw8XFBQBw6NAhuLm5wdfXt8C2bdq0gV6vx7Vr19C5c2eLYyIiebDrh4gsFhERga1bt2LZsmWIjIw0Lm/QoAHWr1+P48eP45dffsGAAQMK3CFUEg0aNEBKSgoSExNx9uxZvP/++0hOTi6wXU5ODgYOHIhTp05h27ZtmDRpEoYNGwYnp4J/2ho1aoSIiAi88sorWL9+PS5duoTk5GTMnDkT33//vcUxEpFtMFEhIov95z//gbe3N1JTUzFgwADj8rlz56Jq1aro2LEjevbsifDwcLRt29bi47/11lvo06cP+vXrhw4dOuDGjRsmrSt5unbtioYNGyI0NBQvvvgievbsidjY2CKPu3z5crzyyisYPXo0GjdujGeffRaHDx+Gn5+fxTESkW1IQgghdxBEREREhWGLChERESkWExUiIiJSLCYqREREpFhMVIiIiEixmKgQERGRYjFRISIiIsViokJERESKxUSFiIiIFIuJChERESkWExUiIiJSLCYqREREpFhMVIiIiEix/h+uXmsal04ZhwAAAABJRU5ErkJggg==", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import seaborn as sns\n", "sns.boxplot(x=\"variable\", y=\"value\", hue=\"species\", data=iris_melted)" ] }, { "cell_type": "markdown", "id": "ffbc76f2-1abf-40a9-bf50-fd61f877ca0d", "metadata": {}, "source": [ "## Practice exercises" ] }, { "cell_type": "markdown", "id": "c60ad539-ffe0-4f86-af38-5dcba24f7562", "metadata": {}, "source": [ "```{exercise}\n", ":label: pandas12\n", "\n", "1- Given the following two dataframes, `df_patients` and `df_conditions`, representing patient information and their diagnosed conditions in a hospital setting respectively, do the following:\n", "\n", "1.1- Use the join method to add the `df_conditions` dataframe to `df_patients`.\n", " - See what happens when you use how='inner'. Which patients remain in the final dataframe?\n", " - See what happens when you use how='outer'. How does the result differ?\n", "\n", "1.2- Use the concat function to vertically stack `df_patients` and `df_conditions`. Why concatenating row-wise might not be very useful here?\n", "\n", "1.3- Use concat to combine `df_patients` and `df_conditions` column-wise. See if the result looks similar to join. What do you notice about alignment?\n", "\n", "```" ] }, { "cell_type": "code", "execution_count": 21, "id": "9fdf231e-2c24-4277-bd7d-8882104f016a", "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "\n", "# DataFrame with patient information\n", "data_patients = {\n", " 'patient_id': [201, 202, 203, 204],\n", " 'age': [55, 63, 45, 70],\n", " 'weight': [68.0, 82.3, 74.5, 60.2]\n", "}\n", "df_patients = pd.DataFrame(data_patients)\n", "df_patients.set_index('patient_id', inplace=True)\n", "\n", "# DataFrame with medical condition details\n", "data_conditions = {\n", " 'patient_id': [201, 202, 205],\n", " 'condition': ['Hypertension', 'Diabetes', 'Chronic Kidney Disease'],\n", " 'treatment_plan': ['Medication', 'Insulin Therapy', 'Dialysis']\n", "}\n", "df_conditions = pd.DataFrame(data_conditions)\n", "df_conditions.set_index('patient_id', inplace=True)" ] }, { "cell_type": "code", "execution_count": 22, "id": "34e72cf1-b600-4985-ab6c-533a665119bd", "metadata": {}, "outputs": [], "source": [ "# Your answers from here" ] }, { "cell_type": "markdown", "id": "1b2779a3-c594-443e-af7a-096472074aac", "metadata": {}, "source": [ "```{exercise}\n", ":label: pandas13\n", "\n", "Use a pivot table to compute the following statistics on `sepal_width` and `petal_width` grouped by species:\n", "\n", "- median \n", "- mean\n", "```" ] }, { "cell_type": "code", "execution_count": 23, "id": "bf2578e6-8241-4909-838b-89f4e8767cc9", "metadata": {}, "outputs": [], "source": [ "# Your answers from here" ] }, { "cell_type": "markdown", "id": "ae2d0103-2665-4445-94d3-44ec67f2ea14", "metadata": {}, "source": [ "```{exercise}\n", ":label: pandas14\n", "\n", "Given the following dataframe, which contains monthly patient visit counts for different departments, reshape it into a long format using `pd.melt()`, so that each row represents the patient count for a department in a particular month. Set the identifier variable as \"Department\" and the values column as \"Patient_Count.\" Check the [documentation](https://pandas.pydata.org/docs/reference/api/pandas.melt.html) to figure out how to do this.\n", "```" ] }, { "cell_type": "code", "execution_count": 24, "id": "ea5ae58e-a2a5-4e56-bbe0-9299aefdce82", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
DepartmentJanFebMar
0Cardiology120150130
1Neurology808590
2Oncology95100110
\n", "
" ], "text/plain": [ " Department Jan Feb Mar\n", "0 Cardiology 120 150 130\n", "1 Neurology 80 85 90\n", "2 Oncology 95 100 110" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Sample data\n", "data = {\n", " 'Department': ['Cardiology', 'Neurology', 'Oncology'],\n", " 'Jan': [120, 80, 95],\n", " 'Feb': [150, 85, 100],\n", " 'Mar': [130, 90, 110]\n", "}\n", "\n", "# Create DataFrame\n", "df = pd.DataFrame(data)\n", "df" ] }, { "cell_type": "code", "execution_count": 25, "id": "b44b2710-adbb-4539-b166-6a65b0cb9baf", "metadata": {}, "outputs": [], "source": [ "# Your answers from here" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.9" } }, "nbformat": 4, "nbformat_minor": 5 }