处理US Baby Names 1880-2010 data set
导入表格数据
import pandas as pd
years = range(1880, 2011)
pieces = []
columns = ['name', 'sex', 'births']
for year in years:
path = 'ch02/names/yob%d.txt' % year
frame = pd.read_csv(path, names=columns)
# 添加year列
frame['year'] = year
pieces.append(frame)
# 把所有数据拼接到一个dataframe里
names = pd.concat(pieces, ignore_index=True)
read_csv本身读进来是一个dataframe,但是因为我们要利用for循环读取多个并将它们依次append于一个list中,所以最后需要利用pd.concat()将list中的所有dataframe合并为一个。 ignore_index=True是为了重置index。
-
names数据
数据聚合与重建
# 在year和sex上对names进行聚合
total_births = names.pivot_table('births', index='year', columns='sex',aggfunc=sum)
# 按性别和年度统计的总出生数
total_births.plot(title='Total births by sex and year')
# 插入prop列,用来存放指定名字的婴儿数相对于总出生数的比例
def add_prop(group):
# 整数除法会向下取整
births = group.births.astype(float)
group['prop'] = births / births.sum()
return group
names = names.groupby(['year', 'sex']).apply(add_prop)
# 检验所有分组的prop总和是否足够近似于1
np.allclose(names.groupby(['year', 'sex']).prop.sum(), 1)
# 取出每对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)
total_births画图(按性别和年度统计的总出生数)
names
-
pandas.DataFrame.groupby
具体参数不在这里赘述,请参考下面文档,这里讲讲自己的试验与理解。
http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.groupby.html
groupby从字面上很好理解,就是我们根据一些columns,来对数据进行group聚合,问题是groupby后的数据到底是什么样的,无法正确理解数据形式就很难进行后面的处理,于是做了试验:
# 因为groupby对象是无法直接输出的,除非使用了sum,mean,size等函数才行,
# 所以我们利用for和append把它的内在结构拉出来看看结果如何
yy = names.groupby(['year','sex'])
xx = []
for y in yy:
xx.append(y)
xx
下面是xx的输出结果,只提取一部分
[((1880, 'F'), 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
... ... ... ... ... ...
940 Vertie F 5 1880 0.000055
941 Wilma F 5 1880 0.000055
[942 rows x 5 columns]),
((1880, 'M'), name sex births year prop
942 John M 9655 1880 0.087381
943 William M 9533 1880 0.086277
那么现在就很明了了,个人理解,groupby对象是一个具有multi-index的dataframe,它是由多个dataframe组合而成,而且每个dataframe具有自己的index,比如这里的话就是[‘year’,’sex’]((1880,’F’)对应的是一个dataframe,(1880,’M’)又对应一个dataframe)。所以当它们应用sum,mean,size之后,就相当于把一个dataframe压缩成了一行,这样拼起来又是一个一般的二维dataframe就可以输出了。
pandas.DataFrame.apply
接下来介绍apply,同样具体参数不在这里赘述了,请参考下面文档,在这里主要讲讲自己的理解。
http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.apply.html
首先提一下关于apply和map的区别,都是映射函数,apply是应用于dataframe的,而map是应用于series的。官方文档中,对apply的描述是将函数应用到dataframe的输入轴向上,对于一般的dataframe(也就是单一index),那么axis=0,就可以理解为是将每个column提出来输入到函数中(也就是一个个Series)。而代码中是将groupby后的对象输入到函数中,上面讲了groupby对象是具有multi-index的,那么这次应用到输入轴向的话,就不再是应用于每个column,而是应用于每个小的dataframe,这样的话,函数add_prop就好理解了。numpy.allclose
https://docs.scipy.org/doc/numpy/reference/generated/numpy.allclose.html
分析命名趋势
# 将前1000个名字分为男女两个部分
boys = top1000[top1000.sex == 'M']
girls = top1000[top1000.sex == 'F']
# 按year和name统计的总出生数透视表
total_births = top1000.pivot_table('births', index='year', columns='name', aggfunc=sum)
# 对这4个名字按年份绘图
subset = total_births[['John', 'Harry', 'Mary', 'Marilyn']]
subset.plot(subplots=True, figsize=(12, 10), grid=False, title="Number of births per year")
### 评估命名多样性的增长
## 方法一
# 计算最流行的1000个名字所占的比例,再按year和sex进行聚合并绘图
table = top1000.pivot_table('prop', index='year', columns='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%的不同名字的数量
def get_quantile_count(group, q=0.5):
group = group.sort_index(by='prop', ascending=False)
return group.prop.cumsum().values.searchsorted(q) + 1
diversity = top1000.groupby(['year', 'sex']).apply(get_quantile_count)
diversity = diversity.unstack('sex')
# diversity画图
diversity.plot(title="Number of popular names in top 50%")
total_births
subset画图(几个男孩和女孩名字随时间变化的使用数量)
table画图(分性别统计的前1000个名字在总出生人数中的比例)
diversity画图(前50%人数中,名字的个数)
numpy.searchsorted(a,v,side=’left’)
在一个sorted的数组a中,找到indices,使得对应的v插入到这些indices之前,a数组的顺序能够不变
https://docs.scipy.org/doc/numpy/reference/generated/numpy.searchsorted.html
最后一个字母的变革
# 从name column里面提取最后一个字母
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', index=last_letters, columns=['sex', 'year'], aggfunc=sum)
# 选出具有一定代表性的年份数据
subtable = table.reindex(columns=[1910, 1960, 2010],level='year')
subtable.sum()
# 求出末位字母所占比例
letter_prop = subtable / subtable.sum().astype(float)
# 生成各年度男孩女孩各个末字母的比例的条形图
fig, axes = plt.subplots(2, 1, figsize=(10, 8))
letter_prop['M'].plot(kind='bar', rot=0, ax=axes[0], title='Male')
letter_prop['F'].plot(kind='bar', rot=0, ax=axes[1], title='Female', legend=False)
# 各年出生的男孩中名字以d/n/y结尾的人数的比例
letter_prop = table / table.sum().astype(float)
dny_ts = letter_prop.ix[['d', 'n', 'y'], 'M'].T
dny_ts.plot()
letter_prop[‘M’]和letter_prop[‘F’]画图(男孩女孩中各个末字母的比例)
dny_ts画图(各年出生的男孩中名字以d/n/y结尾的人数比例)
变成女孩名字的男孩名字(以及相反情况)
# 名字去重
all_names = top1000.name.unique()
# 对all_names的所有元素进行判定,以'lesl'开头的名字将会标为True,其余的为False,也就是说mask是一个布尔量的数组
mask = np.array(['lesl' in x.lower() for x in all_names])
# 再利用布尔索引取出开头为'lesl'的名字
lesley_like = all_names[mask]
# 过滤掉其他名字
filtered = top1000[top1000.name.isin(lesley_like)]
filtered.groupby('name').births.sum()
# 按性别和年度进行聚合,并按年度进行规范化处理,算出比例
table = filtered.pivot_table('births', index='year', columns='sex', aggfunc='sum')
table = table.div(table.sum(1), axis=0)
# 绘制一张分性别的年度曲线图
table.plot(style={'M': 'k-', 'F': 'k--'})
table画图(各年度使用”Lesley型”名字的男女比例)
pandas.DataFrame.div
http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.div.html