关于pandas的问题:扩展多值列、反转和分组
我在研究pandas这个工具,想用它来做一些简单的自然语言处理和文本挖掘的计算,但我有点搞不懂该怎么做。
假设我有一个数据框,里面记录了人们的名字和性别:
import pandas
people = {'name': ['John Doe', 'Mary Poppins', 'Jane Doe', 'John Cusack'], 'gender': ['M', 'F', 'F', 'M']}
df = pandas.DataFrame(people)
我想对每一行做以下几件事:
- 找出名字的第一个名字
- 找出名字中包含的3个字母组合(也就是每个单词中连续的3个字母)
- 统计每个字母组合中,有多少男性和女性的名字包含了这个组合。
我的目标是用这些数据来训练一个分类器,判断一个名字是男性名字还是女性名字。
前两步操作其实挺简单的:
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)
结果是:
> 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']
接下来,我需要构建一个新的数据框,里面有两列:性别和字母组合,内容应该像这样:
gender shingle
0 M joh
1 M ohn
2 F mar
3 F ary
(...)
然后我可以按字母组合和性别进行分组。理想情况下,结果会是:
shingle num_males num_females
0 joh 2 0
1 ohn 2 0
2 mar 0 1
3 ary 0 1
(...)
有没有简单的方法可以把多值的列shingles
展开,让每一行生成多行,每个字母组合对应一行?
另外,如果我对shingle
这一列进行分组,生成每种性别的计数列会有多简单呢?
我已经搞懂了第二部分。举个例子,计算每个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')
然后:
print grouped.apply(countMaleFemale)
females males
first name
Jane 1 0
John 0 2
Mary 1 0
2 个回答
在创建shingles
的时候,直接做扩展版可能会更简单。这个问题展示了如何使用groupby来进行这种扩展。下面是一个在创建“名字”这一列后你可以做的例子:
def shingles(table, n = 3):
word = table['first name'].irow(0)
shingles = [word[i:i + n] for i in range(len(word) - n + 1)]
cols = {col: table[col].irow(0) for col in table.columns}
cols['shingle'] = shingles
return pandas.DataFrame(cols)
>>> df.groupby('name', group_keys=False).apply(shingles)
first name gender name shingle
0 Jane F Jane Doe Jan
1 Jane F Jane Doe ane
0 John M John Cusack Joh
1 John M John Cusack ohn
0 John M John Doe Joh
1 John M John Doe ohn
0 Mary F Mary Poppins Mar
1 Mary F Mary Poppins ary
这里我是按名字分组,而不是按名字的第一部分,主要是为了防止有重复的名字。不过,这里假设全名是唯一的。
接下来,你应该可以根据自己的需要进行分组和计数。
这个方法应该能很好地通用:
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]
首先,创建一个“扩展”的序列,每个条目都是一个“shingle”(可以理解为一个小片段)。在这里,序列的索引是一个多重索引,第一层表示shingle的位置,第二层表示原始数据框(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
接下来,我们可以把创建的序列合并到原始的数据框中。你需要重置索引,去掉shingle位置的那一层。这样得到的序列就有了原始的索引,并且每个shingle都有一个条目。将这个序列合并到原始数据框后,结果是:
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
最后,你可以根据性别进行分组操作,把返回的序列进行“反堆叠”,并用零填充空值(NaN):
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