我来做数据--如何对数据进行处理以满足机器学习技术(一):婴儿名字频率数据

标签(空格分隔): python 数据处理


SSA 提供了一份从1880 到 2010 年的婴儿名字频率数据。我们主要用这份数据来做以下的处理:

  • 计算指定名字(可以是你自己的,也可以是别人的)的年度比例。
  • 计算某个名字的相对排名。
  • 计算各年度最流行的名字,以及增长或减少最快的名字。
  • 分析名字趋势:元音、辅音、长度、总体多样性、拼写变化、首尾字母等
  • 分析外源性趋势:圣经中的名字、名人、人口结构变化等。

1、数据是非常标准的以逗号隔开的格式,所以可以用Pandas.read_csv将其加载到Dataframe中:

>>> names1880 = pd.read_csv('G:\\lcw\\names\\yob1880.txt',names=['name','sex','births'])
#这里的names 是用来作为dataframe的表头
>>> names1880 
           name sex  birth
0          Mary   F   7065
1          Anna   F   2604
2          Emma   F   2003
3     Elizabeth   F   1939
4        Minnie   F   1746
5      Margaret   F   1578
6           Ida   F   1472
7         Alice   F   1414
8        Bertha   F   1320
9         Sarah   F   1288

用births列的sex分组小计表示该年度的births总计:

>>> names1880.groupby('sex')['births'].sum()
sex
F       90993
M      110493
Name: births, dtype: int64

**由于该数据集按年度被分隔成了多个文件,所以第一件事情就是要将所有数据都组装到一个DataFrame里面,并加上一个year字段。使用 pandas.concat 即可达到这个目的:

>>> years = range(1880,2011)
>>> pieces = []
>>> columns = ['name','sex','births']
>>> for year in years:
    path = 'G:\\lcw\\names\\yob%d.txt' % year
    frame = pd.read_csv(path,names = columns)
    frame['year'] = year
    pieces.append (frame)
>>> names = pd.concat(pieces,ignore_index=True)
>>> names
              name sex  births  year
0             Mary   F    7065  1880
1             Anna   F    2604  1880
2             Emma   F    2003  1880
3        Elizabeth   F    1939  1880
4           Minnie   F    1746  1880
5         Margaret   F    1578  1880
6              Ida   F    1472  1880
7            Alice   F    1414  1880
8           Bertha   F    1320  1880
9            Sarah   F    1288  1880
10           Annie   F    1258  1880
11           Clara   F    1226  1880
12            Ella   F    1156  1880
13        Florence   F    1063  1880
14            Cora   F    1045  1880
15          Martha   F    1040  1880
16           Laura   F    1012  1880
17          Nellie   F     995  1880
18           Grace   F     982  1880
19          Carrie   F     949  1880
20           Maude   F     858  1880
21           Mabel   F     808  1880
22          Bessie   F     794  1880
23          Jennie   F     793  1880
24        Gertrude   F     787  1880
25           Julia   F     783  1880
26          Hattie   F     769  1880
27           Edith   F     768  1880
28          Mattie   F     704  1880
29            Rose   F     700  1880
...            ...  ..     ...   ...
1690754    Zaviyon   M       5  2010
1690755   Zaybrien   M       5  2010
1690756   Zayshawn   M       5  2010
1690757     Zayyan   M       5  2010
1690758       Zeal   M       5  2010
1690759     Zealan   M       5  2010
1690760   Zecharia   M       5  2010
  • 合并数据集(pandas.merge 和 pandas.concat)
  • pandas.merge 可根据一个或多个键将不同 DataFrame 中的行连接起来
  • pandas.concat 可以沿着一条轴将多个对象堆叠到一起
  • combine_first 可以用一个对象中的值填充另一个对象中对应位置的缺失值

使用键参数的DataFrame进行合并
pd.merge(left, right, how='inner', on=None, left_on=None, right_on=None, left_index=False, right_index=False, sort=False, suffixes=('_x', '_y'), copy=True) 用于通过一个或多个键将两个数据集的行连接起来,类似于 SQL 中的 JOIN。该函数的典型应用场景是,针对同一个主键存在两张包含不同字段的表,现在我们想把他们整合到一张表里。在此典型情况下,结果集的行数并没有增加,列数则为两个元数据的列数和减去连接键的数量。

on=None 用于显示指定列名(键名),如果该列在两个对象上的列名不同,则可以通过 left_on=None, right_on=None 来分别指定。或者想直接使用行索引作为连接键的话,就将 left_index=False, right_index=False 设为 True。

how='inner' 参数指的是当左右两个对象中存在不重合的键时,取结果的方式:inner 代表交集;outer 代表并集;left 和 right 分别为取一边。

suffixes=('_x','_y') 指的是当左右对象中存在除连接键外的同名列时,结果集中的区分方式,可以各加一个小尾巴。

对于多对多连接,结果采用的是行的笛卡尔积。
轴向连接
merge 算是一种整合的话,轴向连接 pd.concat() 就是单纯地把两个表拼在一起,这个过程也被称作连接(concatenation)、绑定(binding)或堆叠(stacking)。

因此可以想见,这个函数的关键参数应该是 axis,用于指定连接的轴向。在默认的 axis=0 情况下,pd.concat([obj1,obj2]) 函数的效果与 obj1.append(obj2) 是相同的;而在 axis=1 的情况下,pd.concat([df1,df2],axis=1) 的效果与 pd.merge(df1,df2,left_index=True,right_index=True,how='outer') 是相同的。可以理解为 concat 函数使用索引作为“连接键”。

本函数的全部参数为:pd.concat(objs, axis=0, join='outer', join_axes=None, ignore_index=False, keys=None, levels=None, names=None, verify_integrity=False)。

  • objs 就是需要连接的对象集合,一般是列表或字典;axis = 0 是连接轴向
  • join = 'outer' 参数作用于当另一条轴的 index 不重叠的时候,只有'inner' 和 'outer' 可选
    (这里相当于 mysql 里面的内联和外联)
  • join_axes=None 参数用于详细制定其他轴上使用的索引,优先级可以覆盖 join 参数,join_axes 的类型是一个列表,其中的元素为其他轴的 index .
  • keys=None 参数的作用是在结果集中对源数据进行区分。前例中可以看到,结果集中的项无法区分来源,因此使用一个列表型的 keys 参数可以在连接轴上创建一个层次化索引;另一个隐式使用 keys 参数的方法是传入 objs 参数时使用字典,字典的键就会被当做 keys。
  • verify_integrity=False 参数用于检查结果对象新连接轴上的索引是否有重复项,有的话引发 ValueError,可以看到这个参数的作用与 ignore_index 是互斥的。
    (如果 ignore_index = True ,则意味着index不能是重复的,而 = False ,则意味着index可以是重复的)
>>> df1 = DataFrame({'a':range(3),'b':range(3)})
>>> df2 = DataFrame({'a':range(4)})
>>> pd.concat([df1,df2])
   a   b
0  0   0
1  1   1
2  2   2
0  0 NaN
1  1 NaN
2  2 NaN
3  3 NaN
[7 rows x 2 columns]
>>> pd.concat([df1,df2],join='inner',ignore_index=True)
   a
0  0
1  1
2  2
3  0
4  1
5  2
6  3
[7 rows x 1 columns]
  • ps:concat 默认是按行将多个DataFrame组合到一起的;必须指定ignore_index=True,因为我们不希望保留read_csv所返回的原始行号。

2、利用groupby 或 pivot_table在year和sex级别上对其进行聚合了:

>>> total_births = names.pivot_table('births',rows = 'year',cols='sex',aggfunc=sum)
>>> total_births
sex         F        M
year                  
1880    90993   110493
1881    91955   100748
1882   107851   113687
1883   112322   104632
1884   129021   114445
1885   133056   107802
1886   144538   110785
1887   145983   101412
1888   178631   120857
1889   178369   110590
1890   190377   111026
1891   185486   101198
1892   212350   122038
1893   212908   112319
1894   222923   115775
1895   233632   117398

然后,插入一个prop列,用于存放指定名字的婴儿数相对于总出生数的比例。prop值为0.02表示每100名婴儿中有2人取了当前这个名字。因此,我们先按year和sex分组,然后再将新列加到各个分组上:

>>> def add_prop(group):
    births = group.births.astype(float)
    # 由于births是整数,所以我们在计算分式时必须将分子或分母转换成浮点数
    group['prop']= births / births.sum()
    return group
>>> names = names.groupby(['year','sex']).apply(add_prop)
>>> names
              name sex  births  year      prop
0             Mary   F    7065  1880  0.077643
1             Anna   F    2604  1880  0.028618
2             Emma   F    2003  1880  0.022013
3        Elizabeth   F    1939  1880  0.021309
4           Minnie   F    1746  1880  0.019188
5         Margaret   F    1578  1880  0.017342
6              Ida   F    1472  1880  0.016177
7            Alice   F    1414  1880  0.015540
8           Bertha   F    1320  1880  0.014507
9            Sarah   F    1288  1880  0.014155
10           Annie   F    1258  1880  0.013825
11           Clara   F    1226  1880  0.013474
    

检查各个分组总计值是否足够近似于1

>>> np.allclose (names.groupby(['year','sex']).prop.sum(),1)
True

取出数据的一个子集:每对sex/year组合的前1000个名字。又是一个分组操作:

>>> def get_top1000(group):
    return group.sort_index(by='births',ascending=False)[: 1000]
>>> grouped = names.groupby(['year','sex'])
>>> top1000 = grouped.apply(get_top1000)
>>> top1000
                       name sex  births  year      prop
year sex                                               
1880 F   0             Mary   F    7065  1880  0.077643
         1             Anna   F    2604  1880  0.028618
         2             Emma   F    2003  1880  0.022013
         3        Elizabeth   F    1939  1880  0.021309

同样功能的代码:

pieces = []
for year,group in names.groupby(['year','sex']):
    pieces.append(group.sort_index(by='births',ascending=False)[: 1000])
top1000 = pd.concat(pieces, ignore_index=True)

生成得到了完整的数据集和top1000数据集,我们就可以开始分析各种命名趋势了。首先分成男女两个部分:

boys = top1000[top1000.sex == 'M']
girls = top1000[top1000.sex == 'F']
#首先,稍作整理即可绘制出相应的图表(比如每年叫做john和Mary的婴儿数)首先生成一张按照year和name统计的总出生数透视表:
total_births = top1000.pivot_table('births', rows='year', cols='name', aggfunc=sum)
>>> subset = total_births[['John','Harry','Mary','Marilyn']]
>>> subset.plot(subplots=True,figsize=(12,10),grid=False,title="Number of births per year")
>>> from pylab import *
>>> show()

从跳出的图例中可以看出,父母愿意给小孩起常见名字的越来越少,这个可以从数据中得到验证。一个方法就是计算最流行的1000个名字所占的比例,按year和sex进行聚合并绘图:

table = top1000.pivot_table('prop',rows='year',cols='sex' aggfunc=sum)
table.plot(title='Sum of table1000.prop by year and sex',yticks=np.linspace(0,1.2,13), xticks = range(1880,2020,10))

另一个方法是计算占总出生人数前50%的不同名字的数量,我们只考虑2010年男孩的数量:

df = boys[boys.year == 2010]

用numpy 以一种更聪明的矢量方式。先计算prop的累计和cumsum,然后再通过searchsorted方法找出0.5应该被插入到哪个位置才能保证不破坏顺序:

>>> prop_cumsum = df.sort_index(by='prop',ascending=False).prop.cumsum()
>>> prop_cumsum[:10]
year  sex         
2010  M    1676644    0.011523
           1676645    0.020934
           1676646    0.029959
           1676647    0.038930
           1676648    0.047817
           1676649    0.056579
           1676650    0.065155
           1676651    0.073414
           1676652    0.081528
           1676653    0.089621
Name: prop, dtype: float64
>>> prop_cumsum.searchsorted(0.5)
array([116], dtype=int64)
  • numpy.searchsorted 是一个在有序数组上执行二分查找的数组方法,只要将值插入到它返回的那个位置就能维持数组的有序性。
  • 由于数组索引是从0开始的,因此我们要给这个结果加1,即最终结果为117.

现在就可以对所有year/sex组合执行这个计算了,按这两个字段进行groupby处理,然后用一个函数计算各分组的这个值:

>>> def get_quantile_count(group, q=0.5):
    group = group.sort_index(by='prop',ascending=False)
    return group.prop.cumsum().searchsorted(q) +1

>>> diversity = top1000.groupby(['year','sex']).apply(get_quantile_count)
>>> diversity = diversity.unstack('sex')
>>> diversity.head()
sex      F     M
year            
1880  [38]  [14]
1881  [38]  [14]
1882  [38]  [15]
1883  [39]  [15]
1884  [39]  [16]
>>> diversity.plot(title="Number of popular names in top 50%")

3、了解最后一个字母上发生的变化,为了了解具体的情况,首先将全部出生数据在年度、性别以及末字母上进行了聚合:

get_last_letter = lambda x: x[-1]
last_letters = names.name.map(get_last_letter)
last_letters.name = 'last_letter'
table = names.pivot_table('births',rows=last_letters,cols=['sex','year'],aggfunc=sum)
>>> subtable = table.reindex(columns = [1910,1960,2010],level='year')
#选出具有一定代表性的三年,并输出前面几行:
>>> subtable = table.reindex(columns = [1910,1960,2010],level='year' )
>>> subtable.head()
sex               F                      M                
year           1910    1960    2010   1910    1960    2010
last_letter                                               
a            108376  691247  670605    977    5204   28438
b               NaN     694     450    411    3912   38859
c                 5      49     946    482   15476   23125
d              6750    3729    2607  22111  262112   44398
e            133569  435013  313833  28655  178823  129012
>>> 
  • python 支持一种有趣的语法,允许你快速定义单行的最小函数。这些叫做lambda的函数,是从Lisp借用来的,可以用在任何需要函数的地方。
>>> g = lamdba x:x*2
>>> g(3)
6
>>> (lambda x: x*2)(3)
6

1 这是一个 lambda 函数,完成同上面普通函数相同的事情。注意这里的简短的语法:在参数列表周围没有括号,而且忽略了 return 关键字 (隐含存在,因为整个函数只有一行)。而且,该函数没有函数名称,但是可以将它赋值给一个变量进行调用。
2 使用 lambda 函数时甚至不需要将它赋值给一个变量。这可能不是世上最有用的东西,它只是展示了 lambda 函数只是一个内联函数。

Python函数式编程——map()、reduce()
1、map(func,seq1[,seq2...]) Python 函数式编程中的map()函数是将func作用于seq中的每一个元素,并用一个列表给出返回值。如果func为None,作用通zip().
当seq只有一个时,将func函数作用于这个seq的每一个元素上,得到一个新的seq。![map.jpg-47kB][1]
举个例子来说明,(假设我们想要得到一个列表中数字%ia3的余数,那么可以写成下面的代码):

>>> print map(lambda x:x%3, range(6))
>>> [0,1,2,0,1,2]
>>> print [x%3 for x in range(6)]
>>> [0,1,2,0,1,2]

当seq多于一个时,map都可以并行地对每个seq执行如下图所示的过程:
![map2.jpg-53.2kB][2]
下面的例子是求两个列表对应元素的积,可以想象,这是一种可能会经常出现的状况,而如果不是用map的话,就要使用一个for循环,依次对每个位置执行该函数。

>>> print map(lambda x,y:x*y,[1,2,3],[4,5,6])
>>> [4,5,6]
#下面的代码不止实现了乘法,也实现了加法,并把积与和放在一个元组中。
>>> print map(lambda x,y:(x*y,x+y),[1,2,3],[4,5,6])
>>> [(4,5),(10,7),(18,9)]
#当func是None的时候,目的是将多个列表相同的位置的元素归并到一个元组,在现在已经有了专用的函数zip()
>>> print map(None,[1,2,3],[4,5,6])
>>> [(1,4),(2.5),(3,6)]
>>> print zip([1,2,3],[4,5,6])
# 需要注意的是,不同长度的seq是无法执行map函数的,会出现类型错误

2、reduce( func, seq[, init] ) Python
reduce函数即为化简,它是这样一个过程:每次迭代,将上一次的迭代结果(第一次时为init的元素,如没有init则为seq的第一个元素)与下一个元素一同执行一个二元的func函数。在reduce函数中,init是可选的,如果使用,则作为第一次迭代的第一个元素使用。
简单来说,一个形象化的例子:

reduce(func,[1,2,3]) = func(func(1,2),3)

![reduce.jpg-44.9kB][3]
举个例子来说,阶乘是一个常见的数学方法,Python中并没有给出一个阶乘的内建函数,我们可以使用reduce实现一个阶乘的代码:

>>> n=5
>>> print reduce(lambda x,y:x*y,range(1,n+1))
>>> 120
#如果想要得到2倍的阶乘的值,那么就可以用到init这个可选参数了
m=2
n=5
print 
reduce(lambda x,y:x*y,range(1,n+1),m)

然后通过这个字母比例数据之后,就可以生成一张各年度各性别的条形图了:

>>> import matplotlib.pyplot as plt
>>> fig, axes =plt.subplots(2,1, figsize=(10,8))
>>> letter_prop['M'].plot(kind='bar',rot=0,ax=axes[0],title='Male')
<matplotlib.axes._subplots.AxesSubplot object at 0x0000000025C8B9E8>
>>> letter_prop['F'].plot(kind='bar',rot=0,ax=axes[1],title='Female',legend=False)
<matplotlib.axes._subplots.AxesSubplot object at 0x0000000025CAC860>
>>> show()

画图的各个属性:
subplots(numRows,numCols,plotNum)
subplot将整个绘图区域等分为numRows行*numCols列个子区域,然后按照从左到右,从上到下的顺序对每个子区域进行编号。subplot在plotNum指定的区域中创建一个轴对象。figsize就是长宽的比例
![figure_1.png-57.4kB][4]
可以看出,从20世纪60年代开始,以字母"n"结尾的男孩名字出现了显著的增长。最后,回到之前创建的那个table完整表,按年度和性别对其进行规范化处理,并在男孩名字中选取几个字母,最后进行转置以便将各个列做成一个时间序列:

>>> table = names.pivot_table('births',rows=last_letters,cols=['sex','year'],aggfunc=sum)
>>> letter_prop = table / table.sum(). astype(float)
>>> dny_ts = letter_prop.ix[['d','n','y'], 'M']. T
#这里加上了索引和转置。
>>> dny_ts .head()
last_letter         d         n         y
year                                     
1880         0.083055  0.153213  0.075760
1881         0.083247  0.153214  0.077451
1882         0.085340  0.149560  0.077537
1883         0.084066  0.151646  0.079144
1884         0.086120  0.149915  0.080405
dny_ts.plot()

![figure_2.png-48.6kB][5]

4、有趣的趋势:

早年流行于男孩的名字今年来"变性了",例如Lesley或Leslie。回到top1000数据集,找出其中以"les1"开头的一组名字

>>> all_names = top1000.name.unique()
>>> mask = np.array(['lesl' in x.lower() for x in all_names])
>>> lesley_like = all_names[mask]
>>> lesley_like
array(['Leslie', 'Lesley', 'Leslee', 'Lesli', 'Lesly'], dtype=object)
>>> 

然后利用这个结果过滤其他的名字,并按名字分组计算出生数以查看相对频率:

filtered = top1000[top1000.name.isin(lesley_like)]
filtered.groupby('name').births.sum()
  • 不太明白这个地方为什么用sum()
    接下来按性别和年度进行聚合,并按年度进行规范化处理:
>>> table = filtered.pivot_table('births',rows='year',cols='sex',aggfunc='sum')
>>> table = table.div(table.sum(1),axis = 0)
>>> table.tail()
sex   F   M
year       
2006  1 NaN
2007  1 NaN
2008  1 NaN
2009  1 NaN
2010  1 NaN
>>> table.plot(style={'M':'k-','F':'k--'})
<matplotlib.axes._subplots.AxesSubplot object at 0x00000000120D34E0>
>>> show()

这个处理主要用来做什么?暂时还不够清楚
![figure_4.png-43.5kB][6]
[1]: http://static.zybuluo.com/liuchenwei/g3qk08ljnv55dtxlwnk1q29g/map.jpg
[2]: http://static.zybuluo.com/liuchenwei/bbhyhanlkrat0r2betqika4e/map2.jpg
[3]: http://static.zybuluo.com/liuchenwei/0tguh6zi5d6oy8u1iidgoy05/reduce.jpg
[4]: http://static.zybuluo.com/liuchenwei/84hy9onenin70wlmmdwges8y/figure_1.png
[5]: http://static.zybuluo.com/liuchenwei/hmfql71zdp6m0wxfsq0nnz4o/figure_2.png
[6]: http://static.zybuluo.com/liuchenwei/8qv45iwzzkt6qgqxgh7qe3dv/figure_4.png

推荐阅读更多精彩内容