关于pandas的问题:扩展多值列、反转和分组

3 投票
2 回答
4972 浏览
提问于 2025-04-17 18:10

我在研究pandas这个工具,想用它来做一些简单的自然语言处理和文本挖掘的计算,但我有点搞不懂该怎么做。

假设我有一个数据框,里面记录了人们的名字和性别:

import pandas
people = {'name': ['John Doe', 'Mary Poppins', 'Jane Doe', 'John Cusack'], 'gender': ['M', 'F', 'F', 'M']}
df = pandas.DataFrame(people)

我想对每一行做以下几件事:

  1. 找出名字的第一个名字
  2. 找出名字中包含的3个字母组合(也就是每个单词中连续的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 个回答

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

这里我是按名字分组,而不是按名字的第一部分,主要是为了防止有重复的名字。不过,这里假设全名是唯一的。

接下来,你应该可以根据自己的需要进行分组和计数。

7

这个方法应该能很好地通用:

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

撰写回答