Tidy data with Pandas


In [1]:
import pandas as pd

Downloaded tuberculosis (TB) dataset from the World Health Organisation (http://www.who.int/tb/country/data/download/en/).


In [2]:
data = pd.read_csv('data/TB_notifications_2016-12-18.csv')

In [3]:
data.shape


Out[3]:
(7674, 172)

In [4]:
data.head()


Out[4]:
country iso2 iso3 iso_numeric g_whoregion year new_sp new_sn new_su new_ep ... newrel_hivpos newrel_art hivtest hivtest_pos hiv_cpt hiv_art hiv_tbscr hiv_reg hiv_ipt hiv_reg_new
0 Afghanistan AF AFG 4 EMR 1980 NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
1 Afghanistan AF AFG 4 EMR 1981 NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
2 Afghanistan AF AFG 4 EMR 1982 NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
3 Afghanistan AF AFG 4 EMR 1983 NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
4 Afghanistan AF AFG 4 EMR 1984 NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN

5 rows × 172 columns


In [5]:
data.filter(regex='^new').head()


Out[5]:
new_sp new_sn new_su new_ep new_oth newret_oth new_labconf new_clindx new_sp_m04 new_sp_m514 ... newrel_fu newrel_sexunk04 newrel_sexunk514 newrel_sexunk014 newrel_sexunk15plus newrel_sexunkageunk newinc_rdx newrel_hivtest newrel_hivpos newrel_art
0 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
1 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
2 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
3 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
4 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN

5 rows × 112 columns


In [6]:
data.filter(regex='^new.*[0-9]$').head()


Out[6]:
new_sp_m04 new_sp_m514 new_sp_m014 new_sp_m1524 new_sp_m2534 new_sp_m3544 new_sp_m4554 new_sp_m5564 new_sp_m65 new_sp_f04 ... newrel_f014 newrel_f1524 newrel_f2534 newrel_f3544 newrel_f4554 newrel_f5564 newrel_f65 newrel_sexunk04 newrel_sexunk514 newrel_sexunk014
0 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
1 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
2 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
3 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
4 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN

5 rows × 81 columns


In [7]:
# http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.filter.html
data.filter(like='514').head()


Out[7]:
new_sp_m514 new_sp_f514 new_sn_m514 new_sn_f514 new_sn_sexunk514 new_ep_m514 new_ep_f514 new_ep_sexunk514 newrel_m514 newrel_f514 newrel_sexunk514
0 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
1 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
2 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
3 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
4 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN

In [8]:
data.filter(like='514').max()


Out[8]:
new_sp_m514          1594.0
new_sp_f514          3132.0
new_sn_m514          8438.0
new_sn_f514          7322.0
new_sn_sexunk514     4438.0
new_ep_m514          4369.0
new_ep_f514          4055.0
new_ep_sexunk514     5376.0
newrel_m514         36547.0
newrel_f514         45138.0
newrel_sexunk514     3720.0
dtype: float64

In [9]:
(data.filter(like='514') + 1).max()


Out[9]:
new_sp_m514          1595.0
new_sp_f514          3133.0
new_sn_m514          8439.0
new_sn_f514          7323.0
new_sn_sexunk514     4439.0
new_ep_m514          4370.0
new_ep_f514          4056.0
new_ep_sexunk514     5377.0
newrel_m514         36548.0
newrel_f514         45139.0
newrel_sexunk514     3721.0
dtype: float64

In [10]:
# Pass an anonymous function to apply()
data.filter(like='514').apply(lambda x: x + 1).max()


Out[10]:
new_sp_m514          1595.0
new_sp_f514          3133.0
new_sn_m514          8439.0
new_sn_f514          7323.0
new_sn_sexunk514     4439.0
new_ep_m514          4370.0
new_ep_f514          4056.0
new_ep_sexunk514     5377.0
newrel_m514         36548.0
newrel_f514         45139.0
newrel_sexunk514     3721.0
dtype: float64

Subset the data to mimic the who dataset from R package tidyr.


In [11]:
new = data.filter(regex='^new.*(f|m).*[0-9]$')

In [12]:
crit = new.columns.map(lambda x: x.endswith('04') | x.endswith('514'))

In [13]:
new.columns[~crit]


Out[13]:
Index(['new_sp_m014', 'new_sp_m1524', 'new_sp_m2534', 'new_sp_m3544',
       'new_sp_m4554', 'new_sp_m5564', 'new_sp_m65', 'new_sp_f014',
       'new_sp_f1524', 'new_sp_f2534', 'new_sp_f3544', 'new_sp_f4554',
       'new_sp_f5564', 'new_sp_f65', 'new_sn_m014', 'new_sn_m1524',
       'new_sn_m2534', 'new_sn_m3544', 'new_sn_m4554', 'new_sn_m5564',
       'new_sn_m65', 'new_sn_f014', 'new_sn_f1524', 'new_sn_f2534',
       'new_sn_f3544', 'new_sn_f4554', 'new_sn_f5564', 'new_sn_f65',
       'new_ep_m014', 'new_ep_m1524', 'new_ep_m2534', 'new_ep_m3544',
       'new_ep_m4554', 'new_ep_m5564', 'new_ep_m65', 'new_ep_f014',
       'new_ep_f1524', 'new_ep_f2534', 'new_ep_f3544', 'new_ep_f4554',
       'new_ep_f5564', 'new_ep_f65', 'newrel_m014', 'newrel_m1524',
       'newrel_m2534', 'newrel_m3544', 'newrel_m4554', 'newrel_m5564',
       'newrel_m65', 'newrel_f014', 'newrel_f1524', 'newrel_f2534',
       'newrel_f3544', 'newrel_f4554', 'newrel_f5564', 'newrel_f65'],
      dtype='object')

In [14]:
# http://pandas.pydata.org/pandas-docs/stable/merging.html
df = pd.concat([data[['country', 'g_whoregion', 'year']], data[new.columns[~crit]]], axis=1)
df.head()


Out[14]:
country g_whoregion year new_sp_m014 new_sp_m1524 new_sp_m2534 new_sp_m3544 new_sp_m4554 new_sp_m5564 new_sp_m65 ... newrel_m4554 newrel_m5564 newrel_m65 newrel_f014 newrel_f1524 newrel_f2534 newrel_f3544 newrel_f4554 newrel_f5564 newrel_f65
0 Afghanistan EMR 1980 NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
1 Afghanistan EMR 1981 NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
2 Afghanistan EMR 1982 NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
3 Afghanistan EMR 1983 NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
4 Afghanistan EMR 1984 NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN

5 rows × 59 columns

What are the variables in this dataset?


In [15]:
import re

In [16]:
df.rename(columns=lambda x: re.sub('newrel', 'new_rel', x), inplace=True)

In [17]:
# http://pandas.pydata.org/pandas-docs/stable/generated/pandas.melt.html
melted = pd.melt(df, id_vars=['country', 'g_whoregion', 'year'], var_name='key', value_name='cases')

In [18]:
melted.head()


Out[18]:
country g_whoregion year key cases
0 Afghanistan EMR 1980 new_sp_m014 NaN
1 Afghanistan EMR 1981 new_sp_m014 NaN
2 Afghanistan EMR 1982 new_sp_m014 NaN
3 Afghanistan EMR 1983 new_sp_m014 NaN
4 Afghanistan EMR 1984 new_sp_m014 NaN

In [19]:
melted[['new', 'type', 'sexage']] = melted['key'].apply(lambda x: pd.Series(x.split('_')))

In [20]:
melted.head()


Out[20]:
country g_whoregion year key cases new type sexage
0 Afghanistan EMR 1980 new_sp_m014 NaN new sp m014
1 Afghanistan EMR 1981 new_sp_m014 NaN new sp m014
2 Afghanistan EMR 1982 new_sp_m014 NaN new sp m014
3 Afghanistan EMR 1983 new_sp_m014 NaN new sp m014
4 Afghanistan EMR 1984 new_sp_m014 NaN new sp m014

In [21]:
melted.drop('new', axis=1, inplace=True)

In [22]:
melted.drop('key', axis=1, inplace=True)

In [23]:
melted.head()


Out[23]:
country g_whoregion year cases type sexage
0 Afghanistan EMR 1980 NaN sp m014
1 Afghanistan EMR 1981 NaN sp m014
2 Afghanistan EMR 1982 NaN sp m014
3 Afghanistan EMR 1983 NaN sp m014
4 Afghanistan EMR 1984 NaN sp m014

In [24]:
melted[['sex']] = melted['sexage'].apply(lambda x: pd.Series(x[0]))

In [25]:
melted[['age_range']] = melted['sexage'].apply(lambda x: pd.Series(x[1:]))

In [26]:
melted.drop('sexage', axis=1, inplace=True)

In [27]:
melted.head()


Out[27]:
country g_whoregion year cases type sex age_range
0 Afghanistan EMR 1980 NaN sp m 014
1 Afghanistan EMR 1981 NaN sp m 014
2 Afghanistan EMR 1982 NaN sp m 014
3 Afghanistan EMR 1983 NaN sp m 014
4 Afghanistan EMR 1984 NaN sp m 014

In [28]:
melted.tail()


Out[28]:
country g_whoregion year cases type sex age_range
429739 Zimbabwe AFR 2011 NaN rel f 65
429740 Zimbabwe AFR 2012 NaN rel f 65
429741 Zimbabwe AFR 2013 725.0 rel f 65
429742 Zimbabwe AFR 2014 718.0 rel f 65
429743 Zimbabwe AFR 2015 629.0 rel f 65

In [29]:
melted.to_csv('data/tidy_who.csv', index=False)
  • Dataset is in a data frame.
  • Each column is a variable.
  • Each row is an observation.

How about types? Function convert_objects() seems worth looking into.


In [30]:
# http://pandas.pydata.org/pandas-docs/stable/gotchas.html#support-for-integer-na
melted['cases'].dtypes


Out[30]:
dtype('float64')

And?


In [31]:
melted.groupby('type').size()


Out[31]:
type
ep     107436
rel    107436
sn     107436
sp     107436
dtype: int64

In [32]:
melted['type'].value_counts()


Out[32]:
ep     107436
sn     107436
rel    107436
sp     107436
Name: type, dtype: int64

In [33]:
import matplotlib
%matplotlib inline

In [34]:
melted.groupby(('year', 'sex'))['cases'].sum().unstack().plot()


Out[34]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f01df7410b8>