Pandas – Python Data Analysis Library

I’ve recently started using Python’s excellent Pandas library as a data analysis tool, and, while finding the transition from R’s excellent data.table library frustrating at times, I’m finding my way around and finding most things work quite well.

One aspect that I’ve recently been exploring is the task of grouping large data frames by different variables, and applying summary functions on each group. This is accomplished in Pandas using the “groupby()” and “agg()” functions of Panda’s DataFrame objects.

Update: Pandas version 0.20.1 in May 2017 changed the aggregation and grouping APIs. This post has been updated to reflect the new changes.

A Sample DataFrame

Download File IconIn order to demonstrate the effectiveness and simplicity of the grouping commands, we will need some data. For an example dataset, I have extracted my own mobile phone usage records. I analyse this type of data using Pandas during my work on KillBiller. If you’d like to follow along – the full csv file is available here.

The dataset contains 830 entries from my mobile phone log spanning a total time of 5 months. The CSV file can be loaded into a pandas DataFrame using the pandas.DataFrame.from_csv() function, and looks like this:

 

indexdatedurationitemmonthnetworknetwork_type
015/10/14 06:5834.429data2014-11datadata
115/10/14 06:5813.000call2014-11Vodafonemobile
215/10/14 14:4623.000call2014-11Meteormobile
315/10/14 14:484.000call2014-11Tescomobile
415/10/14 17:274.000call2014-11Tescomobile
515/10/14 18:554.000call2014-11Tescomobile
616/10/14 06:5834.429data2014-11datadata
716/10/14 15:01602.000call2014-11Threemobile
816/10/14 15:121050.000call2014-11Threemobile
916/10/14 15:3019.000call2014-11voicemailvoicemail
1016/10/14 16:211183.000call2014-11Threemobile
1116/10/14 22:181.000sms2014-11Meteormobile

The main columns in the file are:

  1. date: The date and time of the entry
  2. duration: The duration (in seconds) for each call, the amount of data (in MB) for each data entry, and the number of texts sent (usually 1) for each sms entry.
  3. item: A description of the event occurring – can be one of call, sms, or data.
  4. month: The billing month that each entry belongs to – of form ‘YYYY-MM’.
  5. network: The mobile network that was called/texted for each entry.
  6. network_type: Whether the number being called was a mobile, international (‘world’), voicemail, landline, or other (‘special’) number.

Phone numbers were removed for privacy. The date column can be parsed using the extremely handy dateutil library.

Summarising the DataFrame

Once the data has been loaded into Python, Pandas makes the calculation of different statistics very simple. For example, mean, max, min, standard deviations and more for columns are easily calculable:

The need for custom functions is minimal unless you have very specific requirements. The full range of basic statistics that are quickly calculable and built into the base Pandas package are:

FunctionDescription
countNumber of non-null observations
sumSum of values
meanMean of values
madMean absolute deviation
medianArithmetic median of values
minMinimum
maxMaximum
modeMode
absAbsolute Value
prodProduct of values
stdUnbiased standard deviation
varUnbiased variance
semUnbiased standard error of the mean
skewUnbiased skewness (3rd moment)
kurtUnbiased kurtosis (4th moment)
quantileSample quantile (value at %)
cumsumCumulative sum
cumprodCumulative product
cummaxCumulative maximum
cumminCumulative minimum

The .describe() function is a useful summarisation tool that will quickly display statistics for any variable or group it is applied to. The describe() output varies depending on whether you apply it to a numeric or character column.

Summarising Groups in the DataFrame

There’s further power put into your hands by mastering the Pandas “groupby()” functionality. Groupby essentially splits the data into different groups depending on a variable of your choice. For example, the expression  data.groupby('month')  will split our current DataFrame by month.

The groupby() function returns a GroupBy object, but essentially describes how the rows of the original data set has been split. the GroupBy object .groups variable is a dictionary whose keys are the computed unique groups and corresponding values being the axis labels belonging to each group. For example:

Functions like max(), min(), mean(), first(), last() can be quickly applied to the GroupBy object to obtain summary statistics for each group – an immensely useful function. This functionality is similar to the dplyr and plyr libraries for R. Different variables can be excluded / included from each summary requirement.

You can also group by more than one variable, allowing more complex queries.

Groupby output format – Series or DataFrame?

The output from a groupby and aggregation operation varies between Pandas Series and Pandas Dataframes, which can be confusing for new users. As a rule of thumb, if you calculate more than one column of results, your result will be a Dataframe. For a single column of results, the agg function, by default, will produce a Series.

You can change this by selecting your operation column differently:

The groupby output will have an index or multi-index on rows corresponding to your chosen grouping variables. To avoid setting this index, pass “as_index=False” to the groupby operation.

Using the as_index parameter while Grouping data in pandas prevents setting a row index on the result.

Multiple Statistics per Group

The final piece of syntax that we’ll examine is the “agg()” function for Pandas. The aggregation functionality provided by the agg() function allows multiple statistics to be calculated per group in one calculation. The syntax is simple, and is similar to that of MongoDB’s aggregation framework.

There were substantial changes to the Pandas aggregation function in May of 2017. Renaming of variables within the agg() function no longer functions as in the diagram below – see notes.

Aggregating statistics for multiple columns in pandas with groupby

Aggregation of variables in a Pandas Dataframe using the agg() function. Note that in Pandas versions 0.20.1 onwards, the renaming of results needs to be done separately.

 

Applying a single function to columns in groups

Instructions for aggregation are provided in the form of a python dictionary or list. The dictionary keys are used to specify the columns upon which you’d like to perform operations, and the dictionary values to specify the function to run.

For example:

The aggregation dictionary syntax is flexible and can be defined before the operation. You can also define functions inline using “lambda” functions to extract statistics that are not provided by the built-in options.

Applying multiple functions to columns in groups

To apply multiple functions to a single column in your grouped data, expand the syntax above to pass in a list of functions as the value in your aggregation dataframe. See below:

The agg(..) syntax is flexible and simple to use. Remember that you can pass in custom and lambda functions to your list of aggregated calculations, and each will be passed the values from the column in your grouped data.

Renaming grouped statistics from groupby operations

When multiple statistics are calculated on columns, the resulting dataframe will have a multi-index set on the column axis. This can be difficult to work with, and I typically have to rename columns after a groupby operation.

One option is to drop the top level (using .droplevel) of the newly created multi-index on columns using:

However, this approach loses the original column names, leaving only the function names as column headers. A neater approach, as suggested to me by a reader, is using the ravel() method on the grouped columns. Ravel() turns a Pandas multi-index into a simpler array, which we can combine into sensible column names:

Quick renaming of grouped columns from the groupby() multi-index can be achieved using the ravel() function.

<DEPRECATED> Dictionary groupby format

In older Pandas releases (< 0.20.1), rename the newly calculated columns was possible through nested dictionaries, or by passing a list of functions for a column. Our final example calculates multiple values from the duration column and names the results appropriately. Note that the results have multi-indexed column headers.

Note this syntax will no longer work for new installations of Python Pandas. 

Aggregation and summarisation of data using pandas python on mobile phone data

Wrap up

The groupby functionality in Pandas is well documented in the official docs and performs at speeds on a par (unless you have massive data and are picky with your milliseconds) with R’s data.table and dplyr libraries.

If you are interested in another example for practice, I used these same techniques to analyse weather data for this post, and I’ve put “how-to” instructions here.

There are plenty of resources online on this functionality, and I’d recommomend really conquering this syntax if you’re using Pandas in earnest at any point.

  • DataQuest Tutorial on Data Analysis: https://www.dataquest.io/blog/pandas-tutorial-python-2/
  • Chris Albon notes on Groups: https://chrisalbon.com/python/pandas_apply_operations_to_groups.html
  • Greg Reda Pandas Tutorial: http://www.gregreda.com/2013/10/26/working-with-pandas-dataframes/
  1. The post is very helpful.
    I have one question here. In the agg statement, how to get quantile 0.05, 0.25, …?

    I tried quantile(.05) in agg, but always got following error:
    AttributeError: ‘SeriesGroupBy’ object has no attribute ‘quantile(0.05)’

    Can someone help on it? Thank you

  2. Thank you Shanelynn. Truly appreciate for the help. ^-^
    The lambda is working well in this way as you suggested.
    Yet, as my aggregations are too many, so I defined a dictionary: dict, and then used in agg(dict) function.
    Others like mean, std are working well, only for quantile, it always prompt error.

    I tried following ways:
    1. dict[‘work_column’] = ‘quantile’ – fine, but no 0.05
    2. dict[‘work_column’] = ‘quantile(0.05)’ – error
    3. dict[‘work_column’] = lambda x: x.quantile(0.05) – error

    So if I use this way, how to solve the error?

  3. Sorry, I made a mistake by missing out one “x” in lambda in my python program. Solution 3: dict[‘work_column’] = lambda x: x.quantile(0.05) is working now.
    Thank you Shanelynn again for the help there.

  4. I would like to convert spark dataframe to pandas data frame for below calculations. would some one help me here please.I am new to pandas dataframe.

    for x in (column name list which is coming from the input parameter)
    value = df.withcolumn(x,df[x]*df[column name which is coming from the input parameter]

    value1 = df.groupby(column name which is coming from the input parameter).agg(*F.sum(x).alias(‘y_’+x) for x in (column name list which is coming from the input parameter),F.sum(y).alias(yy))

    value2 = value1.withcolumn(‘y_’+x,value1[‘y_’+x]/valu1[yy]

  5. Hey shanelynn, I have the same question, it cannot to check the type of the data[‘num_days’], because it is a new variable established in the aggregations. when loading the data at first, the type of the variables (except duration) are all object. Could you kindly help me solve this problems, thanks a lot.

  6. Just found this in the documentation: If you want to be sure the output columns will be in a specific order, you can use an OrderedDict. For example instead of:
    grouped.agg({‘D’: ‘std’, ‘C’: ‘mean’})
    Use:
    grouped.agg(OrderedDict([(‘D’, ‘std’), (‘C’, ‘mean’)]))

    • Maggie – this is a great find, thanks for that. I also see that Pandas has updated some of the API for grouped transactions – I’ll update the text to reflect both asap.

  7. nice, if you could use directly the “date” columnn to do tthe operations instead of creating seperate column called ”month” 🙂 I have the same kind of ts dataset ,having problem with some calculation since it’s not supporting for my timestamp column (ex : timestamp21-06-2016 09:46:34
    ) directly 🙁

  8. Hi, thanks for this post.

    Just to let you know that the dict in dict column renaming feature has been deprecated by Pandas 0.20

    See https://github.com/pandas-dev/pandas/pull/15931 for details, and at the bottom of the thread for kind of a workaround.

    I personnaly find it unpractical to remove this feature, but the devs have already decided on this one…

    • Thanks for this Zertrin – its on my todo list to update this post for the new API changes – hopefully in the coming days to get that sorted. I don’t like the new way either!

  9. Hi, thanks for this post
    I wonder how can group the monthly temperature data accoeding to specific ranges such as (=30C) and find their percentage during the month???

    • Hi Balsam, Good question, and one perhaps that I should cover in the post – I might update it. You can group by arbitrary variables by passing them into the groupby function as such: “data.groupby(data[‘temp’] > 30)” and then apply any function you’d like, in your case probably something like “data.groupby([‘month’, data[‘temp’] > 30])[‘month’].count()” to get counts, and a further step afterwards to get the percentages.

  10. The ravel() function really helps the renaming problem after agg()! Thanks for sharing this post with us.
    Plus, I found your website demonstrates a good display of both pictures and code block, may I know what tools you used?

  11. Very useful article. Thanks. Another nice aggregation function is describe which will dump a table of descriptive statistics. For example:

    data.groupby(‘network’)[‘duration’].describe()

Leave a Reply