Questions about pandas: expanding multivalued column, inverting and grouping

pandas, python

Solution

This method should generalize fairly well:

In [100]: df
Out[100]:
  gender          name firstname    shingles
0      M      John Doe      John  [Joh, ohn]
1      F  Mary Poppins      Mary  [Mar, ary]
2      F      Jane Doe      Jane  [Jan, ane]
3      M   John Cusack      John  [Joh, ohn]

First create an "expanded" series where every entry is a shingle. Here, the index of the series is a multindex where the first level represents the shingle position and the second level represents the index of the original DF:

In [103]: s = df.shingles.apply(lambda x: pandas.Series(x)).unstack();
Out[103]:
0  0    Joh
   1    Mar
   2    Jan
   3    Joh
1  0    ohn
   1    ary
   2    ane
   3    ohn

Next, we can join the created series into the original dataframe. You have to reset the index, dropping the shingle position level. The resulting series has the original index and an entry for each shingle. Merging this into the original dataframe produces:

In [106]: df2 = df.join(pandas.DataFrame(s.reset_index(level=0, drop=True))); df2
Out[106]:
  gender          name firstname    shingles    0
0      M      John Doe      John  [Joh, ohn]  Joh
0      M      John Doe      John  [Joh, ohn]  ohn
1      F  Mary Poppins      Mary  [Mar, ary]  Mar
1      F  Mary Poppins      Mary  [Mar, ary]  ary
2      F      Jane Doe      Jane  [Jan, ane]  Jan
2      F      Jane Doe      Jane  [Jan, ane]  ane
3      M   John Cusack      John  [Joh, ohn]  Joh
3      M   John Cusack      John  [Joh, ohn]  ohn

Finally, you can do your groupby operation on Gender, unstack the returned series and fill the NaN's with zeroes:

In [124]: df2.groupby(0, sort=False)['gender'].value_counts().unstack().fillna(0)
Out[124]:
     F  M
0
Joh  0  2
ohn  0  2
Mar  1  0
ary  1  0
Jan  1  0
ane  1  0

Problem

I was looking into pandas to do some simple calculations on NLP and text mining but I couldn't quite grasp how to do them. Suppose I have the following data frame, relating people's names and their gender: ``` import pandas people = {'name': ['John Doe', 'Mary Poppins', 'Jane Doe', 'John Cusack'], 'gender': ['M', 'F', 'F', 'M']} df = pandas.DataFrame(people) ``` For all rows I want to: - determine the first name - determine a list of 3-shingles (sequences of 3 letters contained in a word) deriving from the person name - determine, for each shingle, how many males and females contained that shingle on their names. The goal is to use this as a data set to train a classifier which can determine if a given name is probably a male or a female name. The first two operations are quite straightforward: ``` def shingles(word, n = 3): return [word[i:i + n] for i in range(len(word) - n + 1)] df['firstname'] = df.name.map(lambda x : x.split()[0]) df['shingles'] = df.firstname.map(shingles) ``` the result is: ``` > print df gender name firstname shingles 0 M John Doe John ['joh', 'ohn'] 1 F Mary Poppins Mary ['mar', 'ary'] 2 F Jane Doe Jane ['jan', 'ane'] 3 M John Cusack John ['joh', 'ohn'] ``` Now, the next step should be done by constructing a new data frame with two columns: gender and shingle, which should contain something like: ``` gender shingle 0 M joh 1 M ohn 2 F mar 3 F ary (...) ``` And then I could group by shingle and gender. Ideally, the result would be: ``` shingle num_males num_females 0 joh 2 0 1 ohn 2 0 2 mar 0 1 3 ary 0 1 (...) ``` Is there an easy way to expand the multivalued column `shingles` in a way that each row produces multiple rows, one for each value found in the list of shingles? Also, if I `groupby` the column `shingle`, how easy it is to produce different columns with the count for each possible value of the column `gender`? I managed to understand the second part. As an example, to calculate how many males and females for each `firstname`: ``` def countMaleFemale(df): return pandas.Series({'males': df.gender[df.gender == 'M'].count(), 'females': df.gender[df.gender == 'F'].count()}) grouped = df.groupby('first name') ``` And then: print grouped.apply(countMaleFemale) ``` females males first name Jane 1 0 John 0 2 Mary 1 0 ```

Original source

Related problems