标签(空格分隔): 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