What is pandas?

A data analysis library for Python that ...

provides rich data structures and functions designed to make working with structured data fast, easy, and expressive.

and ...

combines the high performance array-computing features of NumPy with the flexible data manipulation capabilities of spreadsheets and relational databases

source: Python for Data Analysis - Wes McKinney

What is NumPy?

NumPy is an extension to the Python programming language, adding support for large, multi-dimensional arrays and matrices, along with a large library of high-level mathematical functions to operate on these arrays.

Source: https://en.wikipedia.org/wiki/NumPy

Installation

It's Python 3 compatible!

# python --version
Python 3.3.2

dawp -> Data Analysis with Pandas

# mkproject dawp
# pip -v install ipython[all] pandas
# pip -v install matplotlib numexpr bottleneck

You might want to consider installing scipy as well. Though that requires a Fortran compiler to be present on you system.

On a Mac with MacPorts this can easily be achieved by running:

# sudo port install gcc45
# sudo ln -s /opt/local/bin/gfortran-mp-4.5 /opt/local/bin/gfortran

What's the "ipython[all]" doing in there?

  • Install IPython (obviously)
  • Gets us IPython Notebooks
    • web-based interactive computational environment
    • combines code execution, text, mathematics, plots and rich media into a single document
  • The way to develop your pandas code

BTW this presentation is an IPython Notebook! (and that didn't turn out to be a good choice ;-)). Hence I have removed all slideshow annotations before posting it on my blog.

Pandas Data Structures

  • Series
  • DataFrame
  • Panel (won't cover)
# workon dawp   
# ipython3 notebook --pylab=inline    
In [1]:
import pandas as pd
from pandas import Series, DataFrame

Series Data Structure

  • it's an array like structure
  • in fact, it's backed by a NumPy array

Series - Creation

In [2]:
s = Series(randn(5))
In [3]:
s
Out[3]:
0   -0.029692
1    1.403831
2    0.630169
3   -2.090193
4    1.283291
dtype: float64

Tells us that:

  • 1st column: index
  • 2nd column: values (as produced by randn(5))
  • type of the values in the 2nd column is float64

Create a Series with a specified index

In [4]:
s = Series(randn(5), index=list('abcde'))
In [5]:
s
Out[5]:
a    1.549926
b   -1.648601
c   -0.498359
d   -0.737917
e   -0.381330
dtype: float64

Use a dictionary to create a Series

In [6]:
s = Series(dict(one=7, two=10, three=3, four=9, five=2))
In [7]:
s
Out[7]:
five      2
four      9
one       7
three     3
two      10
dtype: int64

Series - Selection

In [8]:
s['one'] # as a dictionary
Out[8]:
7
In [9]:
s[0] # as a list
Out[9]:
2
In [10]:
s[:3] # slice operation
Out[10]:
five    2
four    9
one     7
dtype: int64
In [11]:
s[s > 5] # boolean based indexing
Out[11]:
four     9
one      7
two     10
dtype: int64

Series - Operations

In [12]:
s.min(), s.max(), s.mean(), s.sum()
Out[12]:
(2, 10, 6.2000000000000002, 31)
In [13]:
s * 2 # vector based operations
Out[13]:
five      4
four     18
one      14
three     6
two      20
dtype: int64

Apply your own Python function to every element of the Series

In [14]:
import string
s.apply(lambda x: string.ascii_letters[x])
Out[14]:
five     c
four     j
one      h
three    d
two      k
dtype: object

Vector operations between two Series

In [15]:
u = Series(randn(5))
v = Series(randn(7))
In [16]:
u + v
Out[16]:
0   -1.676390
1    0.889691
2    0.319059
3   -0.160081
4    1.136108
5         NaN
6         NaN
dtype: float64

Note: Pay attention to how pandas deals with Series of unequal length: it inserts NaN values!

DataFrame Data Structure

  • 2D array like
  • labelled rows and columns
  • heterogeneously typed

DataFrame - Creation

In [17]:
df = DataFrame(dict(foo=[1,2,3,4], 
                    bar=[5.,6.,8.,9.]))
In [18]:
df
Out[18]:
bar foo
0 5 1
1 6 2
2 8 3
3 9 4
In [19]:
df.dtypes
Out[19]:
bar    float64
foo      int64
dtype: object

DataFrame - Creation (cont.)

In [20]:
df1 = DataFrame(dict(foo=[1,2,3,4], 
                     bar=[5.,6.,8.,9.]), 
                index=['one','two','three','four'])
In [21]:
df1
Out[21]:
bar foo
one 5 1
two 6 2
three 8 3
four 9 4
In [22]:
df2 = DataFrame(dict(u=u, v=v)) # dict of Series
In [23]:
df2
Out[23]:
u v
0 -2.649465 0.973074
1 1.459412 -0.569721
2 0.191752 0.127307
3 0.487929 -0.648010
4 0.656887 0.479221
5 NaN -0.623846
6 NaN -0.455265
In [24]:
pd.options.display.max_columns = 30

Construct a DataFrame from a CSV File

In [25]:
df3 = pd.read_csv('04. Inschrijvingen wo_tcm33-32296.csv', 
                  sep=';', encoding='latin-1')
In [26]:
df3.head()
Out[26]:
PROVINCIE GEMEENTENUMMER GEMEENTENAAM BRIN NUMMER ACTUEEL INSTELLINGSNAAM ACTUEEL CROHO ONDERDEEL CROHO SUBONDERDEEL OPLEIDINGSCODE ACTUEEL OPLEIDINGSNAAM ACTUEEL OPLEIDINGSVORM OPLEIDINGSFASE ACTUEEL 2008 MAN 2008 VROUW 2009 MAN 2009 VROUW 2010 MAN 2010 VROUW 2011 MAN 2011 VROUW 2012 MAN 2012 VROUW
0 Gelderland 268 NIJMEGEN 21PM Radboud Universiteit Nijmegen economie n.v.t. (economie) 50645 B Bedrijfskunde voltijd onderwijs bachelor 0 0 287 164 300 187 269 192 254 210
1 Gelderland 268 NIJMEGEN 21PM Radboud Universiteit Nijmegen economie n.v.t. (economie) 50645 B Bedrijfskunde voltijd onderwijs propedeuse bachelor 0 0 347 215 411 244 417 252 283 169
2 Gelderland 268 NIJMEGEN 21PM Radboud Universiteit Nijmegen economie n.v.t. (economie) 50950 B Economie en Bedrijfseconomie voltijd onderwijs bachelor 0 0 0 0 121 53 134 54 123 54
3 Gelderland 268 NIJMEGEN 21PM Radboud Universiteit Nijmegen economie n.v.t. (economie) 50950 B Economie en Bedrijfseconomie voltijd onderwijs propedeuse bachelor 0 0 0 0 140 52 142 46 157 60
4 Gelderland 268 NIJMEGEN 21PM Radboud Universiteit Nijmegen economie n.v.t. (economie) 56401 B Economie voltijd onderwijs bachelor 73 44 92 49 0 0 0 0 0 0

Source: http://data.duo.nl/organisatie/open_onderwijsdata/databestanden/ho/Ingeschreven/ingeschrevenen_wo/wo_inschrijvingen.asp

Het aantal inschrijvingen in het wetenschappelijk onderwijs naar studiejaar en geslacht. Er worden vijf studiejaren gepresenteerd. De aantallen staan weergegeven per provincie, gemeente, instelling, croho (sub)onderdeel, opleiding, opleidingsvorm en opleidingsfase.

DataFrame - Selection

In [27]:
df1['bar'] # column selection
Out[27]:
one      5
two      6
three    8
four     9
Name: bar, dtype: float64
In [28]:
df1.bar # column selection via attribute
Out[28]:
one      5
two      6
three    8
four     9
Name: bar, dtype: float64
In [29]:
df1.loc['one'] # row selection by label
Out[29]:
bar    5
foo    1
Name: one, dtype: float64
In [30]:
df1.iloc[0] # row selection by integer
Out[30]:
bar    5
foo    1
Name: one, dtype: float64
In [31]:
df1[1:3] # slice rows
Out[31]:
bar foo
two 6 2
three 8 3
In [32]:
df1[df1['bar'] > 6] # select rows by boolean vector
Out[32]:
bar foo
three 8 3
four 9 4

DataFrame - Addition & Deletion

In [33]:
df1['foobar'] = df1['foo'] + df1['bar']
In [34]:
df1
Out[34]:
bar foo foobar
one 5 1 6
two 6 2 8
three 8 3 11
four 9 4 13
In [35]:
df1['zero'] = 0 # assign a scalar value to a whole column
In [36]:
df1
Out[36]:
bar foo foobar zero
one 5 1 6 0
two 6 2 8 0
three 8 3 11 0
four 9 4 13 0
In [37]:
del df1['foobar']
In [38]:
df1
Out[38]:
bar foo zero
one 5 1 0
two 6 2 0
three 8 3 0
four 9 4 0

DataFrame - Merging/Joining

In [39]:
# straight from the pandas' documentation
left = DataFrame({'key1': ['foo', 'foo', 'bar'],
                  'key2': ['one', 'two', 'one'],
                  'lval': [1, 2, 3]})
 

right = DataFrame({'key1': ['foo', 'foo', 'bar', 'bar'],
                   'key2': ['one', 'one', 'one', 'two'],
                   'rval': [4, 5, 6, 7]})
In [40]:
left
Out[40]:
key1 key2 lval
0 foo one 1
1 foo two 2
2 bar one 3
In [41]:
right
Out[41]:
key1 key2 rval
0 foo one 4
1 foo one 5
2 bar one 6
3 bar two 7
In [42]:
pd.merge(left, right, how='outer')
Out[42]:
key1 key2 lval rval
0 foo one 1 4
1 foo one 1 5
2 foo two 2 NaN
3 bar one 3 6
4 bar two NaN 7
In [43]:
pd.merge(left, right, how='inner')
Out[43]:
key1 key2 lval rval
0 foo one 1 4
1 foo one 1 5
2 bar one 3 6

Some basic data analysis

Total number of students

In [44]:
wo = df3 # wo -> Wetenschappelijk Onderwijs
In [45]:
wo.rename(columns=str.lower, inplace=True)
In [46]:
wo.columns
Out[46]:
Index(['provincie', 'gemeentenummer', 'gemeentenaam', 'brin nummer actueel', 'instellingsnaam actueel', 'croho onderdeel', 'croho subonderdeel', 'opleidingscode actueel', 'opleidingsnaam actueel', 'opleidingsvorm', 'opleidingsfase actueel', '2008 man', '2008 vrouw', '2009 man', '2009 vrouw', '2010 man', '2010 vrouw', '2011 man', '2011 vrouw', '2012 man', '2012 vrouw'], dtype=object)
In [47]:
men = [col for col in wo.columns.tolist() if col.endswith('man')]
women = [col for col in wo.columns.tolist() if col.endswith('vrouw')]
In [48]:
uni = wo[['instellingsnaam actueel', 'croho onderdeel'] + men + women]
In [49]:
uni.head()
Out[49]:
instellingsnaam actueel croho onderdeel 2008 man 2009 man 2010 man 2011 man 2012 man 2008 vrouw 2009 vrouw 2010 vrouw 2011 vrouw 2012 vrouw
0 Radboud Universiteit Nijmegen economie 0 287 300 269 254 0 164 187 192 210
1 Radboud Universiteit Nijmegen economie 0 347 411 417 283 0 215 244 252 169
2 Radboud Universiteit Nijmegen economie 0 0 121 134 123 0 0 53 54 54
3 Radboud Universiteit Nijmegen economie 0 0 140 142 157 0 0 52 46 60
4 Radboud Universiteit Nijmegen economie 73 92 0 0 0 44 49 0 0 0
In [50]:
for name, group in uni.groupby('instellingsnaam actueel'):
    pass
In [51]:
name
Out[51]:
'Wageningen University'
In [52]:
group.head()
Out[52]:
instellingsnaam actueel croho onderdeel 2008 man 2009 man 2010 man 2011 man 2012 man 2008 vrouw 2009 vrouw 2010 vrouw 2011 vrouw 2012 vrouw
257 Wageningen University landbouw en natuurlijke omgeving 1 0 0 0 0 0 0 0 0 0
258 Wageningen University landbouw en natuurlijke omgeving 2 0 0 0 0 0 0 0 0 0
259 Wageningen University landbouw en natuurlijke omgeving 2 0 0 0 0 0 0 0 0 0
260 Wageningen University landbouw en natuurlijke omgeving 0 0 0 0 0 0 1 0 0 0
261 Wageningen University landbouw en natuurlijke omgeving 0 2 0 0 0 0 1 0 0 0
In [53]:
# math operations ignore nuisance columns (non-numeric columns)
uni_size = uni.groupby('instellingsnaam actueel').sum()
In [54]:
uni_size.head()
Out[54]:
2008 man 2009 man 2010 man 2011 man 2012 man 2008 vrouw 2009 vrouw 2010 vrouw 2011 vrouw 2012 vrouw
instellingsnaam actueel
Erasmus Universiteit Rotterdam 12827 12940 12888 11920 12026 10644 11177 11433 10980 10872
NHTV Breda 0 9 10 30 39 0 14 17 82 97
Radboud Universiteit Nijmegen 7574 7828 7993 8020 7531 11469 11783 12022 11647 11110
Rijksuniversiteit Groningen 13039 13945 13855 13967 13655 13960 15002 15082 15119 14736
Techn. Universiteit Eindhoven 6451 6552 6512 6584 6499 1312 1382 1468 1444 1555
In [55]:
uni_size.index.name = 'Instelling'
In [56]:
uni_size['2008'] = uni_size['2008 man'] + uni_size['2008 vrouw']
uni_size['2009'] = uni_size['2009 man'] + uni_size['2009 vrouw']
uni_size['2010'] = uni_size['2010 man'] + uni_size['2010 vrouw']
uni_size['2011'] = uni_size['2011 man'] + uni_size['2011 vrouw']
uni_size['2012'] = uni_size['2012 man'] + uni_size['2012 vrouw']
In [57]:
uni_size = uni_size[['2008','2009','2010','2011','2012']] # select only relevant columns
In [58]:
uni_size.sort(columns='2012', ascending=False).head()
Out[58]:
2008 2009 2010 2011 2012
Instelling
Universiteit van Amsterdam 30348 33336 35643 34587 31858
Universiteit Utrecht 31988 32700 32179 31973 30907
Rijksuniversiteit Groningen 26999 28947 28937 29086 28391
Vrije Universiteit Amsterdam 22079 24078 25761 26087 24780
Erasmus Universiteit Rotterdam 23471 24117 24321 22900 22898
In [59]:
uni_size.sort(axis=1, ascending=False) # axis=0 -> by row, axis=1 -> by column
Out[59]:
2012 2011 2010 2009 2008
Instelling
Erasmus Universiteit Rotterdam 22898 22900 24321 24117 23471
NHTV Breda 136 112 27 23 0
Radboud Universiteit Nijmegen 18641 19667 20015 19611 19043
Rijksuniversiteit Groningen 28391 29086 28937 28947 26999
Techn. Universiteit Eindhoven 8054 8028 7980 7934 7763
Technische Universiteit Delft 18779 19142 19241 17926 16565
Universiteit Leiden 22603 21643 21667 20548 19638
Universiteit Maastricht 14973 14946 14783 14510 13412
Universiteit Twente 9472 9618 9158 8842 8486
Universiteit Utrecht 30907 31973 32179 32700 31988
Universiteit van Amsterdam 31858 34587 35643 33336 30348
Universiteit van Tilburg 13978 14827 14593 14031 13370
Vrije Universiteit Amsterdam 24780 26087 25761 24078 22079
Wageningen University 7539 7128 6529 5765 5211
In [60]:
uni_size.sort(axis=1, 
              ascending=False).sort(columns='2012', 
                                    ascending=False).plot(kind='barh', 
                                                          figsize=[10,10])
Out[60]:
<matplotlib.axes.AxesSubplot at 0x10e765710>

Studies with largest men/women differences

In [61]:
opl = wo.groupby('opleidingsnaam actueel').sum()
opl = opl[['2012 man', '2012 vrouw']]
opl['diff'] = opl['2012 man'] - opl['2012 vrouw']
sorted_opl = opl.sort(columns='diff', ascending=False)

top5_max = sorted_opl[:5]
top5_min = sorted_opl[-5:]
top5 = pd.concat([top5_max, top5_min])

top5['diff'].plot(kind='barh')
Out[61]:
<matplotlib.axes.AxesSubplot at 0x10e843950>

Random stuff

Another boolean indexing example

In [62]:
cs_crit = wo['opleidingsnaam actueel'].str.contains('Informatica')
cs = wo[cs_crit]
In [63]:
cs['opleidingsnaam actueel'].value_counts()
Out[63]:
B Informatica                     12
B Technische Informatica           8
Technische Informatica             6
Informatica                        4
M Informatica                      2
B Economie en Informatica          2
M Lerarenopleiding Informatica     1
dtype: int64

Pivot tabels

In [64]:
pv = pd.pivot_table(wo, values=['2012 man', '2012 vrouw'], 
                    rows=['instellingsnaam actueel'], 
                    cols=['croho onderdeel'], 
                    fill_value=0, aggfunc=np.sum)
                    
In [65]:
pv.head(5)
Out[65]:
2012 man 2012 vrouw
croho onderdeel economie gedrag en maatschappij gezondheidszorg landbouw en natuurlijke omgeving natuur onderwijs recht sectoroverstijgend taal en cultuur techniek economie gedrag en maatschappij gezondheidszorg landbouw en natuurlijke omgeving natuur onderwijs recht sectoroverstijgend taal en cultuur techniek
instellingsnaam actueel
Erasmus Universiteit Rotterdam 7475 1145 1355 0 0 0 1544 0 500 7 3688 2197 2480 0 0 0 1943 0 558 6
NHTV Breda 0 18 0 0 0 0 0 21 0 0 0 28 0 0 0 0 0 69 0 0
Radboud Universiteit Nijmegen 1174 1545 1082 0 1586 78 985 0 1081 0 736 3689 2115 0 931 78 1611 0 1950 0
Rijksuniversiteit Groningen 4186 1667 1324 0 1903 118 1599 0 2104 754 2090 3417 2451 0 1291 164 2098 0 3110 115
Techn. Universiteit Eindhoven 0 0 0 0 0 41 0 0 0 6458 0 0 0 0 0 12 0 0 0 1543

Stacking/Unstacking

In [66]:
pv.stack(1).head()
Out[66]:
2012 man 2012 vrouw
instellingsnaam actueel croho onderdeel
Erasmus Universiteit Rotterdam economie 7475 3688
gedrag en maatschappij 1145 2197
gezondheidszorg 1355 2480
landbouw en natuurlijke omgeving 0 0
natuur 0 0
In [67]:
pv.loc['Universiteit van Amsterdam']
Out[67]:
            croho onderdeel                 
2012 man    economie                            2650
            gedrag en maatschappij              2727
            gezondheidszorg                     1248
            landbouw en natuurlijke omgeving       0
            natuur                              2152
            onderwijs                            125
            recht                               1573
            sectoroverstijgend                   365
            taal en cultuur                     2795
            techniek                               0
2012 vrouw  economie                            1448
            gedrag en maatschappij              5703
            gezondheidszorg                     2077
            landbouw en natuurlijke omgeving       0
            natuur                              1344
            onderwijs                            124
            recht                               2305
            sectoroverstijgend                   497
            taal en cultuur                     4725
            techniek                               0
Name: Universiteit van Amsterdam, dtype: int64
In [68]:
pv.loc['Universiteit van Amsterdam'].unstack()
Out[68]:
croho onderdeel economie gedrag en maatschappij gezondheidszorg landbouw en natuurlijke omgeving natuur onderwijs recht sectoroverstijgend taal en cultuur techniek
2012 man 2650 2727 1248 0 2152 125 1573 365 2795 0
2012 vrouw 1448 5703 2077 0 1344 124 2305 497 4725 0

Transpose rows/columns

In [69]:
pv.loc['Universiteit van Amsterdam'].unstack().T
Out[69]:
2012 man 2012 vrouw
croho onderdeel
economie 2650 1448
gedrag en maatschappij 2727 5703
gezondheidszorg 1248 2077
landbouw en natuurlijke omgeving 0 0
natuur 2152 1344
onderwijs 125 124
recht 1573 2305
sectoroverstijgend 365 497
taal en cultuur 2795 4725
techniek 0 0

Want to learn more?

Buy & study this book:

Python for Data Analysis