目录
- 导入数据
- 数据聚合+分析前准备
- 分析趋势
- 评估多样性
导入数据
继续上一节,python3.7, maxOS + jupyter notebook 参考的是*《利用Python进行数据分析》*一书,这次使用的是1880-2010年间全美婴儿姓名,下载估计需要但我还是提供网址https://www.ssa.gov/oact/babynames/limits.html 然后选择“National data”(如果不能下载可以私戳我)解压后是很多很多的txt文档,相比前一个movie的数据集,这个数据集体量更大并且具有明确的时间区分。
由于这是一个非常标准的以逗号隔开的格式,所以使用pandas.read_csv将其加载到DataFrame中:
import pandas as pd
names1880 = pd.read_csv('/Users/wangchenghang/Desktop/names/yob1880.txt',names=['name', 'sex', 'births'])
这些文件中仅含当年出现超过5次的名字。为了简单起见,我们可以用births列的sex分组表示该年度的births总计:
names1880.groupby('sex').births.sum()
结果如下:
数据聚合+分析前准备
由于该数据集按年度被分隔成了多个文件,所以第一件事就是要将所有数据都组装到一个DataFrame里面,并加上一个year的字段。 使用pandas.concat即可达到这个目的:
#2019是目前最后一个有效年份
years = range(1880,2019)
pieces = []
columns = ['name', 'sex', 'births']
for year in years:
path = '/Users/vicky/Desktop/names/yob%d.txt' %year
frame = pd.read_csv(path, names = columns)
frame['year'] = year
pieces.append(frame)
#将数据整合到单个dataframe中
names = pd.concat(pieces, ignore_index= True)
这里需要注意:
- concat默认是按行将多个Dataframe组合到一起的
- igonre_index=True,是因为我们不希望保留read_csv所返回的原始行号
现在得到的巨大的Dataframe如下图所示:
有了这些数据后,我们就可以使用groupby或pivot_table在year和sex级别上对其进行聚合了:
total_births = names.pivot_table('births', index='year', columns='sex', aggfunc=sum)
顺便可视化一下:
total_births.plot(title='Total births by sex and year')
下面可以继续插入一个prop列,用于存放指定名字的婴儿数相对总出生数的比例。prop值为0.02表示100名婴儿中有2名取了当前的名字。因此,我们先按year和sex分组,然后再将新列加到各个分组上:
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,由于是float型,这里使用的是np.allclose :
看起来没有错误👍 但为了实现更进一步的分析, 还需要取出该数据的一个子集:每对sex/year组合的前1000个名字。这又是一个分组操作:
def get_top1000(group):
return group.sort_values(by='births',ascending=False)[:1000]
grouped = names.groupby(['year','sex'])
top1000 = grouped.apply(get_top1000)
得到如下Dataframe:
分析趋势
有了完整的数据集和刚才生成的top1000数据集,我们可以开始分析各种命名趋势了。首先将前1000个名字分为男女两个部分:
boys = top1000[top1000.sex == 'M']
girls = top1000[top1000.sex == 'F']
我们先生成一张按year和name统计的总出生透视表:
top1000.index = top1000.index.droplevel()
total_births = top1000.pivot_table('births', index = 'year', columns='name' , aggfunc=sum)
total_births
得到:
我们再用plot方法绘制几个名字的曲线:
subset = total_births[['John','Harry','Mary','Marilyn']]
subset.plot(subplots=True, figsize=(12,10),grid=False,title='Number of births per year')
评估多样性
这个其实牵扯一个思路问题,虽然书上写的思路很直接,但其实如果直观的去解决这个问题还是有些麻烦的。 基于之前top1000的表,那如果最流行的1000个名字所占的比例减少,那么也可以侧面证明:
top1000.index = top1000.index.droplevel()
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,2030,20)
得到的图为:
确实前1000个流行的名字所占比例在下降。 但考虑另一个方法是计算占出生人数前50%的不同名字的数量(因为计算比较复杂,这里取1990年和2018年做比较):
可以明显的看出,名字的数量增长了许多。既然这个方法看起来是可行的,我们就可以对所有year/sex组合执行这个计算。按这两个字段进行groupby处理,然后用一个函数计算各分组的这个值:
def get_quantile_count(group, q=0.5):
gruop = group.sort_values(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.plot(title = 'Number of popular names in top 50%', yticks = np.linspace(0, 300, 13), xticks = range(1880,2030,20))
结果如下: