{ "cells": [ { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Grouping" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "import numpy as np\n", "import pandas as pd\n", "import seaborn as sns\n", "import matplotlib.pyplot as plt\n", "%matplotlib inline\n", "\n", "pd.options.display.max_rows = 6\n", "pd.options.display.max_columns = 6\n", "pd.options.display.width = 80" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "We'll use the same dataset of beer reviews." ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false }, "outputs": [], "source": [ "df = pd.read_hdf('data/beer.hdf')" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Groupby" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Groupby is a fundamental operation to pandas and data analysis.\n", "\n", "The components of a groupby operation are to\n", "\n", "1. Split a table into groups\n", "2. Apply a function to each groups\n", "3. Combine the results\n", "\n", "http://pandas.pydata.org/pandas-docs/stable/groupby.html" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "In pandas the first step looks like" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "``df.groupby( grouper )``" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "`grouper` can be many things\n", "\n", "- ``Series`` (or string indicating a column in a ``DataFrame``)\n", "- function (to be applied on the index)\n", "- dict : groups by *values*\n", "- `levels=[]`, names of levels in a MultiIndex" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "gr = df.groupby('beer_style')\n", "gr" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Haven't really done anything yet. Just some book-keeping to figure out which **keys** go with which rows. Keys are the things we've grouped by (each `beer_style` in this case)." ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ "104" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "gr.ngroups" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "['Smoked Beer',\n", " 'Altbier',\n", " 'Braggot',\n", " 'Berliner Weissbier',\n", " 'American Amber / Red Ale']" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "list(gr.groups)[0:5]" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "['beer_style',\n", " 'abv',\n", " 'beer_id',\n", " 'beer_name',\n", " 'brewer_id',\n", " 'profile_name',\n", " 'review_appearance',\n", " 'review_aroma',\n", " 'review_overall',\n", " 'review_palate',\n", " 'review_taste',\n", " 'text',\n", " 'time']" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cols = ['beer_style'] + df.columns.difference(['beer_style']).tolist()\n", "cols" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false, "slideshow": { "slide_type": "subslide" } }, "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", "
beer_styleabvbeer_id...review_tastetexttime
42Tripel8.01631...4.0Was perusing a store for some new items, and ...2009-10-05 22:12:23
79Tripel8.55426...4.0Appearance: Pours a very cloudy chestnut colo...2009-10-05 23:02:42
102Tripel10.02566...4.0Poured a nice slightly cloudy golden yellow w...2009-10-05 23:31:27
........................
49882Tripel8.01631...4.0Pours a golden amber, with a big head that st...2009-12-25 05:02:03
49945Tripel7.81054...3.5A- It's pretty. Nice golden yellow. Nice and ...2009-12-25 08:41:01
49949Tripel8.0658...3.5From a 750 ml caged and corked brown bottle. ...2009-12-25 09:39:25
\n", "

809 rows × 13 columns

\n", "
" ], "text/plain": [ " beer_style abv beer_id ... review_taste \\\n", "42 Tripel 8.0 1631 ... 4.0 \n", "79 Tripel 8.5 5426 ... 4.0 \n", "102 Tripel 10.0 2566 ... 4.0 \n", "... ... ... ... ... ... \n", "49882 Tripel 8.0 1631 ... 4.0 \n", "49945 Tripel 7.8 1054 ... 3.5 \n", "49949 Tripel 8.0 658 ... 3.5 \n", "\n", " text time \n", "42 Was perusing a store for some new items, and ... 2009-10-05 22:12:23 \n", "79 Appearance: Pours a very cloudy chestnut colo... 2009-10-05 23:02:42 \n", "102 Poured a nice slightly cloudy golden yellow w... 2009-10-05 23:31:27 \n", "... ... ... \n", "49882 Pours a golden amber, with a big head that st... 2009-12-25 05:02:03 \n", "49945 A- It's pretty. Nice golden yellow. Nice and ... 2009-12-25 08:41:01 \n", "49949 From a 750 ml caged and corked brown bottle. ... 2009-12-25 09:39:25 \n", "\n", "[809 rows x 13 columns]" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "gr.get_group('Tripel')[cols]" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false, "slideshow": { "slide_type": "fragment" } }, "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", "
beer_styleabvbeer_id...review_tastetexttime
42Tripel8.01631...4.0Was perusing a store for some new items, and ...2009-10-05 22:12:23
79Tripel8.55426...4.0Appearance: Pours a very cloudy chestnut colo...2009-10-05 23:02:42
102Tripel10.02566...4.0Poured a nice slightly cloudy golden yellow w...2009-10-05 23:31:27
........................
49882Tripel8.01631...4.0Pours a golden amber, with a big head that st...2009-12-25 05:02:03
49945Tripel7.81054...3.5A- It's pretty. Nice golden yellow. Nice and ...2009-12-25 08:41:01
49949Tripel8.0658...3.5From a 750 ml caged and corked brown bottle. ...2009-12-25 09:39:25
\n", "

809 rows × 13 columns

\n", "
" ], "text/plain": [ " beer_style abv beer_id ... review_taste \\\n", "42 Tripel 8.0 1631 ... 4.0 \n", "79 Tripel 8.5 5426 ... 4.0 \n", "102 Tripel 10.0 2566 ... 4.0 \n", "... ... ... ... ... ... \n", "49882 Tripel 8.0 1631 ... 4.0 \n", "49945 Tripel 7.8 1054 ... 3.5 \n", "49949 Tripel 8.0 658 ... 3.5 \n", "\n", " text time \n", "42 Was perusing a store for some new items, and ... 2009-10-05 22:12:23 \n", "79 Appearance: Pours a very cloudy chestnut colo... 2009-10-05 23:02:42 \n", "102 Poured a nice slightly cloudy golden yellow w... 2009-10-05 23:31:27 \n", "... ... ... \n", "49882 Pours a golden amber, with a big head that st... 2009-12-25 05:02:03 \n", "49945 A- It's pretty. Nice golden yellow. Nice and ... 2009-12-25 08:41:01 \n", "49949 From a 750 ml caged and corked brown bottle. ... 2009-12-25 09:39:25 \n", "\n", "[809 rows x 13 columns]" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df.loc[df.beer_style=='Tripel',cols]" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "The last two steps, apply and combine:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false, "slideshow": { "slide_type": "fragment" } }, "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", "
abvbeer_idbrewer_id...review_overallreview_palatereview_taste
beer_style
Altbier5.93633128241.6983244724.486034...3.7905033.6899443.703911
American Adjunct Lager4.8805529403.6380701541.485255...3.0717162.7332442.674933
American Amber / Red Ale6.13596323557.4691453314.523420...3.8338293.6947963.734944
........................
Wheatwine10.76666744146.6416182363.132948...3.7225433.8728323.947977
Winter Warmer6.52040722726.7506032315.001609...3.8053103.7035403.767900
Witbier5.85054529447.0239043096.407703...3.7423643.5677293.624170
\n", "

104 rows × 8 columns

\n", "
" ], "text/plain": [ " abv beer_id brewer_id ... \\\n", "beer_style ... \n", "Altbier 5.936331 28241.698324 4724.486034 ... \n", "American Adjunct Lager 4.880552 9403.638070 1541.485255 ... \n", "American Amber / Red Ale 6.135963 23557.469145 3314.523420 ... \n", "... ... ... ... ... \n", "Wheatwine 10.766667 44146.641618 2363.132948 ... \n", "Winter Warmer 6.520407 22726.750603 2315.001609 ... \n", "Witbier 5.850545 29447.023904 3096.407703 ... \n", "\n", " review_overall review_palate review_taste \n", "beer_style \n", "Altbier 3.790503 3.689944 3.703911 \n", "American Adjunct Lager 3.071716 2.733244 2.674933 \n", "American Amber / Red Ale 3.833829 3.694796 3.734944 \n", "... ... ... ... \n", "Wheatwine 3.722543 3.872832 3.947977 \n", "Winter Warmer 3.805310 3.703540 3.767900 \n", "Witbier 3.742364 3.567729 3.624170 \n", "\n", "[104 rows x 8 columns]" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "gr.agg('mean')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This says apply the `mean` function to each column. Non-numeric columns (nusiance columns) are excluded. We can also select a subset of columns to perform the aggregation on." ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": false, "slideshow": { "slide_type": "subslide" } }, "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", "
abvreview_overallreview_appearancereview_palatereview_taste
beer_style
Altbier5.9363313.7905033.7765363.6899443.703911
American Adjunct Lager4.8805523.0717162.7835122.7332442.674933
American Amber / Red Ale6.1359633.8338293.8364313.6947963.734944
..................
Wheatwine10.7666673.7225433.9161853.8728323.947977
Winter Warmer6.5204073.8053103.8644413.7035403.767900
Witbier5.8505453.7423643.6347943.5677293.624170
\n", "

104 rows × 5 columns

\n", "
" ], "text/plain": [ " abv review_overall review_appearance \\\n", "beer_style \n", "Altbier 5.936331 3.790503 3.776536 \n", "American Adjunct Lager 4.880552 3.071716 2.783512 \n", "American Amber / Red Ale 6.135963 3.833829 3.836431 \n", "... ... ... ... \n", "Wheatwine 10.766667 3.722543 3.916185 \n", "Winter Warmer 6.520407 3.805310 3.864441 \n", "Witbier 5.850545 3.742364 3.634794 \n", "\n", " review_palate review_taste \n", "beer_style \n", "Altbier 3.689944 3.703911 \n", "American Adjunct Lager 2.733244 2.674933 \n", "American Amber / Red Ale 3.694796 3.734944 \n", "... ... ... \n", "Wheatwine 3.872832 3.947977 \n", "Winter Warmer 3.703540 3.767900 \n", "Witbier 3.567729 3.624170 \n", "\n", "[104 rows x 5 columns]" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "review_columns = ['abv','review_overall','review_appearance',\n", " 'review_palate','review_taste']\n", "gr[review_columns].agg('mean')" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "`.` attribute lookup works as well." ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "beer_style\n", "Altbier 5.936331\n", "American Adjunct Lager 4.880552\n", "American Amber / Red Ale 6.135963\n", " ... \n", "Wheatwine 10.766667\n", "Winter Warmer 6.520407\n", "Witbier 5.850545\n", "Name: abv, dtype: float64" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "gr.abv.agg('mean')" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Find the `beer_style` with the greatest variance in `abv`." ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "beer_style\n", "American Strong Ale 3.506721\n", "American Double / Imperial Stout 2.785742\n", "Flanders Oud Bruin 2.723145\n", " ... \n", "Gose 0.185557\n", "Low Alcohol Beer 0.121006\n", "Happoshu 0.000000\n", "Name: abv, dtype: float64" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(df\n", " .groupby('beer_style')\n", " .abv\n", " .std()\n", " .sort_values(ascending=False)\n", " )" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Multiple Aggregations on one column" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
meanstdcount
beer_style
Altbier3.5810060.561276179
American Adjunct Lager2.4798930.732925746
American Amber / Red Ale3.6535320.5882311345
............
Wheatwine3.9421970.484780173
Winter Warmer3.7389380.5794631243
Witbier3.6195220.589968753
\n", "

104 rows × 3 columns

\n", "
" ], "text/plain": [ " mean std count\n", "beer_style \n", "Altbier 3.581006 0.561276 179\n", "American Adjunct Lager 2.479893 0.732925 746\n", "American Amber / Red Ale 3.653532 0.588231 1345\n", "... ... ... ...\n", "Wheatwine 3.942197 0.484780 173\n", "Winter Warmer 3.738938 0.579463 1243\n", "Witbier 3.619522 0.589968 753\n", "\n", "[104 rows x 3 columns]" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "gr['review_aroma'].agg([np.mean, np.std, 'count'])" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Single Aggregation on multiple columns" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
abvreview_overallreview_appearancereview_palatereview_taste
beer_style
Altbier5.9363313.7905033.7765363.6899443.703911
American Adjunct Lager4.8805523.0717162.7835122.7332442.674933
American Amber / Red Ale6.1359633.8338293.8364313.6947963.734944
..................
Wheatwine10.7666673.7225433.9161853.8728323.947977
Winter Warmer6.5204073.8053103.8644413.7035403.767900
Witbier5.8505453.7423643.6347943.5677293.624170
\n", "

104 rows × 5 columns

\n", "
" ], "text/plain": [ " abv review_overall review_appearance \\\n", "beer_style \n", "Altbier 5.936331 3.790503 3.776536 \n", "American Adjunct Lager 4.880552 3.071716 2.783512 \n", "American Amber / Red Ale 6.135963 3.833829 3.836431 \n", "... ... ... ... \n", "Wheatwine 10.766667 3.722543 3.916185 \n", "Winter Warmer 6.520407 3.805310 3.864441 \n", "Witbier 5.850545 3.742364 3.634794 \n", "\n", " review_palate review_taste \n", "beer_style \n", "Altbier 3.689944 3.703911 \n", "American Adjunct Lager 2.733244 2.674933 \n", "American Amber / Red Ale 3.694796 3.734944 \n", "... ... ... \n", "Wheatwine 3.872832 3.947977 \n", "Winter Warmer 3.703540 3.767900 \n", "Witbier 3.567729 3.624170 \n", "\n", "[104 rows x 5 columns]" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "gr[review_columns].mean()" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true, "slideshow": { "slide_type": "subslide" } }, "source": [ "Multiple aggregations on multiple columns" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
characteristicabv...review_taste
measuremeancountstd...meancountstd
beer_style
Altbier5.9363311691.394085...3.7039111790.608033
American Adjunct Lager4.8805527060.405311...2.6749337460.815884
American Amber / Red Ale6.13596312561.299676...3.73494413450.640439
........................
Wheatwine10.7666671711.043411...3.9479771730.624275
Winter Warmer6.52040712281.079628...3.76790012430.621533
Witbier5.8505457161.561416...3.6241707530.674517
\n", "

104 rows × 15 columns

\n", "
" ], "text/plain": [ "characteristic abv ... review_taste \\\n", "measure mean count std ... mean \n", "beer_style ... \n", "Altbier 5.936331 169 1.394085 ... 3.703911 \n", "American Adjunct Lager 4.880552 706 0.405311 ... 2.674933 \n", "American Amber / Red Ale 6.135963 1256 1.299676 ... 3.734944 \n", "... ... ... ... ... ... \n", "Wheatwine 10.766667 171 1.043411 ... 3.947977 \n", "Winter Warmer 6.520407 1228 1.079628 ... 3.767900 \n", "Witbier 5.850545 716 1.561416 ... 3.624170 \n", "\n", "characteristic \n", "measure count std \n", "beer_style \n", "Altbier 179 0.608033 \n", "American Adjunct Lager 746 0.815884 \n", "American Amber / Red Ale 1345 0.640439 \n", "... ... ... \n", "Wheatwine 173 0.624275 \n", "Winter Warmer 1243 0.621533 \n", "Witbier 753 0.674517 \n", "\n", "[104 rows x 15 columns]" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "result = gr[review_columns].agg(['mean', 'count', 'std'])\n", "result.columns.names=['characteristic','measure']\n", "result" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Hierarchical Indexes in the columns can be awkward to work with, so I'll usually\n", "move a level to the Index with `.stack`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "http://pandas.pydata.org/pandas-docs/stable/reshaping.html#reshaping-by-stacking-and-unstacking" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "collapsed": false, "slideshow": { "slide_type": "subslide" } }, "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", "
characteristicabv...review_taste
measuremeancountstd...meancountstd
beer_style
Altbier5.9363311691.394085...3.7039111790.608033
American Adjunct Lager4.8805527060.405311...2.6749337460.815884
American Amber / Red Ale6.13596312561.299676...3.73494413450.640439
........................
Wheatwine10.7666671711.043411...3.9479771730.624275
Winter Warmer6.52040712281.079628...3.76790012430.621533
Witbier5.8505457161.561416...3.6241707530.674517
\n", "

104 rows × 15 columns

\n", "
" ], "text/plain": [ "characteristic abv ... review_taste \\\n", "measure mean count std ... mean \n", "beer_style ... \n", "Altbier 5.936331 169 1.394085 ... 3.703911 \n", "American Adjunct Lager 4.880552 706 0.405311 ... 2.674933 \n", "American Amber / Red Ale 6.135963 1256 1.299676 ... 3.734944 \n", "... ... ... ... ... ... \n", "Wheatwine 10.766667 171 1.043411 ... 3.947977 \n", "Winter Warmer 6.520407 1228 1.079628 ... 3.767900 \n", "Witbier 5.850545 716 1.561416 ... 3.624170 \n", "\n", "characteristic \n", "measure count std \n", "beer_style \n", "Altbier 179 0.608033 \n", "American Adjunct Lager 746 0.815884 \n", "American Amber / Red Ale 1345 0.640439 \n", "... ... ... \n", "Wheatwine 173 0.624275 \n", "Winter Warmer 1243 0.621533 \n", "Witbier 753 0.674517 \n", "\n", "[104 rows x 15 columns]" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "result" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "collapsed": false, "slideshow": { "slide_type": "subslide" } }, "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", "
measuremeancountstd
beer_stylecharacteristic
Altbierabv5.9363311691.394085
review_overall3.7905031790.674136
review_appearance3.7765361790.578459
...............
Witbierreview_appearance3.6347947530.581826
review_palate3.5677297530.615671
review_taste3.6241707530.674517
\n", "

520 rows × 3 columns

\n", "
" ], "text/plain": [ "measure mean count std\n", "beer_style characteristic \n", "Altbier abv 5.936331 169 1.394085\n", " review_overall 3.790503 179 0.674136\n", " review_appearance 3.776536 179 0.578459\n", "... ... ... ...\n", "Witbier review_appearance 3.634794 753 0.581826\n", " review_palate 3.567729 753 0.615671\n", " review_taste 3.624170 753 0.674517\n", "\n", "[520 rows x 3 columns]" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "multi = result.stack(level='characteristic')\n", "multi" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
characteristicabvreview_overallreview_appearancereview_palatereview_taste
beer_stylemeasure
Altbiermean5.9363313.7905033.7765363.6899443.703911
count169.000000179.000000179.000000179.000000179.000000
std1.3940850.6741360.5784590.5832010.608033
.....................
Witbiermean5.8505453.7423643.6347943.5677293.624170
count716.000000753.000000753.000000753.000000753.000000
std1.5614160.7005110.5818260.6156710.674517
\n", "

312 rows × 5 columns

\n", "
" ], "text/plain": [ "characteristic abv review_overall review_appearance \\\n", "beer_style measure \n", "Altbier mean 5.936331 3.790503 3.776536 \n", " count 169.000000 179.000000 179.000000 \n", " std 1.394085 0.674136 0.578459 \n", "... ... ... ... \n", "Witbier mean 5.850545 3.742364 3.634794 \n", " count 716.000000 753.000000 753.000000 \n", " std 1.561416 0.700511 0.581826 \n", "\n", "characteristic review_palate review_taste \n", "beer_style measure \n", "Altbier mean 3.689944 3.703911 \n", " count 179.000000 179.000000 \n", " std 0.583201 0.608033 \n", "... ... ... \n", "Witbier mean 3.567729 3.624170 \n", " count 753.000000 753.000000 \n", " std 0.615671 0.674517 \n", "\n", "[312 rows x 5 columns]" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "result.stack(level='measure')" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "collapsed": false, "slideshow": { "slide_type": "subslide" } }, "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", "
characteristicabv...review_taste
measuremeancountstd...meancountstd
beer_style
Altbier5.9363311691.394085...3.7039111790.608033
American Adjunct Lager4.8805527060.405311...2.6749337460.815884
American Amber / Red Ale6.13596312561.299676...3.73494413450.640439
........................
Wheatwine10.7666671711.043411...3.9479771730.624275
Winter Warmer6.52040712281.079628...3.76790012430.621533
Witbier5.8505457161.561416...3.6241707530.674517
\n", "

104 rows × 15 columns

\n", "
" ], "text/plain": [ "characteristic abv ... review_taste \\\n", "measure mean count std ... mean \n", "beer_style ... \n", "Altbier 5.936331 169 1.394085 ... 3.703911 \n", "American Adjunct Lager 4.880552 706 0.405311 ... 2.674933 \n", "American Amber / Red Ale 6.135963 1256 1.299676 ... 3.734944 \n", "... ... ... ... ... ... \n", "Wheatwine 10.766667 171 1.043411 ... 3.947977 \n", "Winter Warmer 6.520407 1228 1.079628 ... 3.767900 \n", "Witbier 5.850545 716 1.561416 ... 3.624170 \n", "\n", "characteristic \n", "measure count std \n", "beer_style \n", "Altbier 179 0.608033 \n", "American Adjunct Lager 746 0.815884 \n", "American Amber / Red Ale 1345 0.640439 \n", "... ... ... \n", "Wheatwine 173 0.624275 \n", "Winter Warmer 1243 0.621533 \n", "Witbier 753 0.674517 \n", "\n", "[104 rows x 15 columns]" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# stack-unstack are inverses\n", "(result\n", " .stack(level='measure')\n", " .unstack(level='measure')\n", " )" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "You can group by **levels** of a MultiIndex." ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
minmax
beer_style
Altbier3.6899445.936331
American Adjunct Lager2.6749334.880552
American Amber / Red Ale3.6947966.135963
.........
Wheatwine3.72254310.766667
Winter Warmer3.7035406.520407
Witbier3.5677295.850545
\n", "

104 rows × 2 columns

\n", "
" ], "text/plain": [ " min max\n", "beer_style \n", "Altbier 3.689944 5.936331\n", "American Adjunct Lager 2.674933 4.880552\n", "American Amber / Red Ale 3.694796 6.135963\n", "... ... ...\n", "Wheatwine 3.722543 10.766667\n", "Winter Warmer 3.703540 6.520407\n", "Witbier 3.567729 5.850545\n", "\n", "[104 rows x 2 columns]" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(result.stack(level='characteristic')\n", " .groupby(level='beer_style')\n", " ['mean']\n", " .agg(['min', 'max' ])\n", " )" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Group by **multiple** columns" ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
abvreview_overallreview_appearancereview_palatereview_taste
brewer_idbeer_style
1Czech Pilsener4.3892863.8753.5892863.5714293.660714
3American Adjunct Lager4.2000003.7503.2500003.5000002.750000
American Amber / Red Ale5.2000003.7503.6944443.6388893.611111
.....................
24964American Malt Liquor6.9000003.2503.0000002.7500003.250000
25680Euro Pale Lager5.3000003.5004.0000003.5000003.500000
27039American Double / Imperial IPA9.9900004.7504.5000004.7500004.750000
\n", "

7580 rows × 5 columns

\n", "
" ], "text/plain": [ " abv review_overall \\\n", "brewer_id beer_style \n", "1 Czech Pilsener 4.389286 3.875 \n", "3 American Adjunct Lager 4.200000 3.750 \n", " American Amber / Red Ale 5.200000 3.750 \n", "... ... ... \n", "24964 American Malt Liquor 6.900000 3.250 \n", "25680 Euro Pale Lager 5.300000 3.500 \n", "27039 American Double / Imperial IPA 9.990000 4.750 \n", "\n", " review_appearance review_palate \\\n", "brewer_id beer_style \n", "1 Czech Pilsener 3.589286 3.571429 \n", "3 American Adjunct Lager 3.250000 3.500000 \n", " American Amber / Red Ale 3.694444 3.638889 \n", "... ... ... \n", "24964 American Malt Liquor 3.000000 2.750000 \n", "25680 Euro Pale Lager 4.000000 3.500000 \n", "27039 American Double / Imperial IPA 4.500000 4.750000 \n", "\n", " review_taste \n", "brewer_id beer_style \n", "1 Czech Pilsener 3.660714 \n", "3 American Adjunct Lager 2.750000 \n", " American Amber / Red Ale 3.611111 \n", "... ... \n", "24964 American Malt Liquor 3.250000 \n", "25680 Euro Pale Lager 3.500000 \n", "27039 American Double / Imperial IPA 4.750000 \n", "\n", "[7580 rows x 5 columns]" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df.groupby(['brewer_id', 'beer_style'])[review_columns].mean()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Exercise: Plot the relationship between review length (the `text` column) and average `review_overall`.\n", "\n", "- Find the **len**gth of each reivew (remember the `df.text.str` namespace?)\n", "- Group by that Series of review lengths\n", "- Using the '.k' plotting style" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "collapsed": false, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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\nS6eifQ9TXNwHC3/fsM/+4y+++IpOn75fko5Upghn2t3v0tEPNv7iHi7lxG3vn69/LD+1I/Z6+32p\n2ubGjZu9lBS/9vALL1zXnTt3+vog6Uh6TXiN/H7u7e313UAo6ci18H93zp9f710DV/s57MeJEyej\n3z5U3czpf8ja3r5+pB0nrFXtH0NS3+sUW0jGP9bW1tW+98FwmzCVKTdPPfw3lfr3U9VubCEcf9+6\n49dJlXCs6kPTY9Qdv4122jjmpD+AD/Pa5vY9dwyjPMkcYGPMz0r6q9bajxljviLpA9Zae/jc2yT9\nl9ba/+Tw91+X9P9aa59MHnBhYV8af85jbMbRf/MPi/yPcmGK2Mxm1Q1UqYUMqtqvOpfYcQbpW84+\nYSBZFSCHr8Pi4jFduvR07bmF/Ysdw88dju1XtXiFv2943KpjVe0/aJpDqm/jkHueiAvz3Qd9LV2g\nW3VzZt2S3HXHdh8Mqu4HyHnvCNOQwhtD/Xbq2o2lT9XlyYf/Pp977tnK/M7Ue0hVH9r8m5B730ab\nqo45ib7k9Ctnmyb3v8TGGjnA5RgmB/jnJO0bYzYk/Zikzxtj/tZhmsPLkpa9bZclfSe3U4uLx7Sy\nsly/YUsWIpfAHf+5557VxYsXJUlXrlzp/SwdzDbl9NPfP7VNVUDhX4/Ydv7NWT/0Q2d07ty5vr66\n44bn4tpz5cUWF4/p0Uffpeeff17nzp3rtX/lypXevv5KbLG+ue2Xl5d1587rs2QnT57s/X7y5Mle\n+88++2zlNTl58mSv7cXFY5LUd73947nnY9eu6vGVleXK52NjItw3bL+qrabHrpPq2zgM2m8cNcxr\nubBw8O9h0DZy9ou91lWPxd4L/W3D44Xt1LUbPu+3l3P82HtI3fHr+hD7fdC/XWFfx/E3sOqYk+hL\nTr9ytsnte+q1G/f5YvpkV4E4nAF+zN0Ed5gD/E1Jj+ggP/iKpPdYa/8y1c7DDz+yL01XCkRqW6n+\n03Hup1G/TTfz4oSzvzkzbzmzI7ltxkqghX2rKmnm9yd1M13q2JL6ZpGcsHKF/9Vl7CvKcNGJcEnj\nML0h/Do09bVseC3aTIEIc41JgSAFghSIwVIg6mb3SIGYbF+aHn8UKRDMAJejlTJoLgVC0nlJp6y1\nTxhj3i3pIzpYUONJa+3jde3MShm0Jl8PDfJ1TE6bTqzUl9R+AOykbq7LqSghNVuhLWefnHONfU3s\n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\nQgAMAACAohAAAwAAoCgEwAAAACgKATAAAACKQgAMAACAohAAAwAAoCgEwAAAACgKATAAAACKQgAM\nAACAohAAAwAAoCgEwAAAACgKATAAAACKQgAMAACAohAAAwAAoCgEwAAAACgKATAAAACKQgAMAACA\nohAAAwAAoCgEwAAAACgKATAAAACKQgAMAACAohAAAwAAoCgEwAAAACgKATAAAACKQgAMAACAohAA\nAwAAoCgEwAAAACgKATAAAACKQgAMAACAohAAAwAAoCgEwAAAACgKATAAAACKQgAMAACAohAAAwAA\noCjHU08aYxYlfVbSD0r6fkkftdb+kff8eyR9WNKrkj5rrf3MCPsKAAAADK1uBvinJe1Ya98u6W9K\n+p/cE4fB8Scl/YSkd0j6BWPMg6PqKAAAANCG5AywpN+X9AeHP3d0MNPrrEn6trX2ZUkyxnxV0tu9\n7QHMiW53Q5K0uXl5Ysd0vz/33LNj60Mb2rh2k7j+ADDPkgGwtXZXkowxyzoIhv+R9/QbJL3s/X5b\n0n1tdxDAZHW7G9rautr7eRxBWHhMSb3fL168qEuXnh55H9rQxrWbxPUHgHlXNwMsY8wPSPqSpE9b\na/+J99TLkpa935clfaeuvQceOKnjx4817ScKsrKyXL8RxmZx8Vjfz+N4fcJjhmZljLRx7SZx/Wcd\n1wh1GCNY2N/fr3zSGPNGSX8i6e9ba78SPLco6ZuSHpG0K+mKpPdYa/8ydcCdndvVB0TxVlaWtbNz\ne9LdQGDaUiBmaYyQAjFevIegDmOkHCsrywtVz9UFwJ+S9HclWe/hJyQtWWufMMa8W9JHdJAf/KS1\n9vG6zhAAI4U3JtRhjCCF8YE6jJFyDBwAjwIBMFJ4Y0IdxghSGB+owxgpRyoAZiEMAAAAFIUAGAAA\nAEUhAAYAAEBRCIABAABQFAJgAAAAFIUAGAAAAEUhAAYAAEBRCIABAABQFAJgAAAAFIUAGAAAAEUh\nAAYAAEBRCIABAABQFAJgAAAAFIUAGAAAAEUhAAYAAEBRCIABAABQFAJgAAAAFIUAGAAAAEUhAAYA\nAEBRCIABAABQFAJgAAAAFIUAGAAAAEUhAAYAAEBRCIABAABQFAJgAAAAFIUAGAAAAEUhAAYAAEBR\nCIABAABQFAJgAAAAFIUAGAAAAEUhAAYAAEBRCIABAABQFAJgAAAAFIUAGAAAAEUhAAYAAEBRCIAB\n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\n", "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "(df.groupby(df.text.str.len())\n", " .review_overall\n", " .mean()\n", " .plot(style='.k', figsize=(12,8))\n", " )\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## What are we doing" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "collapsed": false, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "0 364\n", "1 590\n", "2 1786\n", " ... \n", "49997 714\n", "49998 427\n", "49999 1018\n", "Name: text, dtype: int64" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df.text.str.len()" ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "collapsed": false, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "2201" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df.groupby(df.text.str.len()).ngroups" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "We've seen a lot of permutations among number of groupers, number of columns to aggregate, and number of aggregators.\n", "In fact, the `.agg`, which returns one row per group, is just one kind of way to combine the results. The three ways are\n", "\n", "- `agg`: one row per results\n", "- `transform`: identicaly shaped output as input\n", "- `apply`: anything goes\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Transform" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Combined Series / DataFrame is the same shape as the input. For example, say you want to standardize the reviews by subtracting the mean." ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "collapsed": true, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "def de_mean(reviews):\n", " s = reviews - reviews.mean()\n", " return s" ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "collapsed": false, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "0 0.65018\n", "1 0.65018\n", "2 -0.84982\n", " ... \n", "49997 1.15018\n", "49998 0.15018\n", "49999 1.15018\n", "Name: review_overall, dtype: float64" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "de_mean(df.review_overall)" ] }, { "cell_type": "code", "execution_count": 27, "metadata": { "collapsed": false, "slideshow": { "slide_type": "subslide" } }, "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", "
abvbeer_idbrewer_id...review_overallreview_palatereview_taste
0-2.763000-14386.800000-250.800000...0.500000-0.1000000.450000
1-1.501563-12613.3333333168.025641...0.3974360.038462-0.012821
2-2.072727-10567.348485805.833333...-0.803030-0.568182-0.287879
........................
499970.4000004817.3888891236.833333...0.9166670.7777780.555556
49998-1.625862-36774.032258-4302.516129...-0.112903-0.838710-0.774194
499992.16550020759.95000012161.450000...0.9250000.5750000.375000
\n", "

50000 rows × 8 columns

\n", "
" ], "text/plain": [ " abv beer_id brewer_id ... review_overall \\\n", "0 -2.763000 -14386.800000 -250.800000 ... 0.500000 \n", "1 -1.501563 -12613.333333 3168.025641 ... 0.397436 \n", "2 -2.072727 -10567.348485 805.833333 ... -0.803030 \n", "... ... ... ... ... ... \n", "49997 0.400000 4817.388889 1236.833333 ... 0.916667 \n", "49998 -1.625862 -36774.032258 -4302.516129 ... -0.112903 \n", "49999 2.165500 20759.950000 12161.450000 ... 0.925000 \n", "\n", " review_palate review_taste \n", "0 -0.100000 0.450000 \n", "1 0.038462 -0.012821 \n", "2 -0.568182 -0.287879 \n", "... ... ... \n", "49997 0.777778 0.555556 \n", "49998 -0.838710 -0.774194 \n", "49999 0.575000 0.375000 \n", "\n", "[50000 rows x 8 columns]" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df.groupby('profile_name').transform(de_mean)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Oftentimes is better to work with the groupby object directly" ] }, { "cell_type": "code", "execution_count": 28, "metadata": { "collapsed": false, "slideshow": { "slide_type": "fragment" } }, "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", "
abvbeer_idbrewer_id...review_overallreview_palatereview_taste
0-2.763000-14386.800000-250.800000...0.500000-0.1000000.450000
1-1.501563-12613.3333333168.025641...0.3974360.038462-0.012821
2-2.072727-10567.348485805.833333...-0.803030-0.568182-0.287879
........................
499970.4000004817.3888891236.833333...0.9166670.7777780.555556
49998-1.625862-36774.032258-4302.516129...-0.112903-0.838710-0.774194
499992.16550020759.95000012161.450000...0.9250000.5750000.375000
\n", "

50000 rows × 8 columns

\n", "
" ], "text/plain": [ " abv beer_id brewer_id ... review_overall \\\n", "0 -2.763000 -14386.800000 -250.800000 ... 0.500000 \n", "1 -1.501563 -12613.333333 3168.025641 ... 0.397436 \n", "2 -2.072727 -10567.348485 805.833333 ... -0.803030 \n", "... ... ... ... ... ... \n", "49997 0.400000 4817.388889 1236.833333 ... 0.916667 \n", "49998 -1.625862 -36774.032258 -4302.516129 ... -0.112903 \n", "49999 2.165500 20759.950000 12161.450000 ... 0.925000 \n", "\n", " review_palate review_taste \n", "0 -0.100000 0.450000 \n", "1 0.038462 -0.012821 \n", "2 -0.568182 -0.287879 \n", "... ... ... \n", "49997 0.777778 0.555556 \n", "49998 -0.838710 -0.774194 \n", "49999 0.575000 0.375000 \n", "\n", "[50000 rows x 8 columns]" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(df-df.groupby('profile_name').transform('mean')\n", ").select_dtypes(exclude=['object'])" ] }, { "cell_type": "code", "execution_count": 29, "metadata": { "collapsed": false, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1 loops, best of 3: 4.76 s per loop\n" ] } ], "source": [ "%timeit df.groupby('profile_name').transform(de_mean)" ] }, { "cell_type": "code", "execution_count": 30, "metadata": { "collapsed": false, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1 loops, best of 3: 2.43 s per loop\n" ] } ], "source": [ "%timeit (df-df.groupby('profile_name').transform('mean')).select_dtypes(exclude=['object'])" ] }, { "cell_type": "code", "execution_count": 31, "metadata": { "collapsed": false, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "4124" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df.groupby('profile_name').ngroups" ] } ], "metadata": { "celltoolbar": "Slideshow", "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.4.3" } }, "nbformat": 4, "nbformat_minor": 0 }