《Python for Data Analysis》一书由Wes Mckinney所著,中文译名是《利用Python进行数据分析》。这里记录一下学习过程,其中有些方法和书中不同,是按自己比较熟悉的方式实现的。

第三个实例:US Baby Names 1880-2010

简介: 美国社会保障总署(SSA)提供了一份从1880年到2010年的婴儿姓名频率的数据

数据地址: https://github.com/wesm/pydata-book/tree/2nd-edition/datasets/babynames

准备工作:导入pandas和matplotlib

import pandas as pd
import matplotlib.pyplot as plt
fig,ax=plt.subplots()

我们现在拥有的数据文件是从1880年-2010年的婴儿姓名频率的.txt文件,文件如下所示:

Mary,F,7065
Anna,F,2604
Emma,F,2003
Elizabeth,F,1939
Minnie,F,1746

其中第一列是姓名,第二列是性别,第三列是当年此性别婴儿中取该名字的人数。

文件内容以逗号分隔,因此可以用pandas的read_csv方法读取:

file=pd.read_csv(r"...\yob1880.txt", header=None, names=["Name", "Gender", "Birth"])

现在读取的是1880年的文件,文件没有列名,因此header设为None,同时自己设置列名分别为Name, Gender和Birth。

我们需要给文件加上年份这一列,以便知道某姓名频次是属于哪一年的:

file["Year"]=1880

现在读取的1880年的文件显示如下:

        Name Gender  Birth  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

但是我们有很多份这样的文件,不可能一个一个地手动读取。因此,我们利用循环语句来读取每份文件,并把这些文件用concat命令合并在一起。

file_list=[]  #设置空列表,作为合并列表

for year in range(1880,2011):
file=pd.read_csv(r"...\yob{}.txt".format(year), header=None, names=["Name", "Gender", "Birth"])
file["Year"]=year
file_list.append(file) data=pd.concat(file_list, ignore_index=True)

concat命令可以把pandas对象按行或列合并起来,命令接收的是一个序列。因此,我们首先设置一个空列表,然后循环读取文件,把文件变为pandas对象,并放入该列表中。这里注意,我们不需要保留原来的索引,因此设置ignore_index参数为True。

接下来,我们就可以开始分析了。

让我们先来看看每一年的男女出生总人数分别是多少。

total_birth=pd.pivot_table(data, values="Birth", index="Year", columns="Gender", aggfunc="sum")
Gender       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

用折线图画出来:

ax.plot(range(1880,2011), total_birth["F"], label="Female")
ax.plot(range(1880,2011), total_birth["M"], label="Male")
ax.legend()
ax.set_xticks(range(1880,2021,20))
ax.set_xlim(1880,2020) plt.show()

可以看出,每一年男女出生人数都差不多。

接下来,我们想知道某个名字在某年某性别出生婴儿中占比多少。比如:Mary在1880年女性姓名中占比多少,假设结果是0.02,那就说明每100个1880年出生的女婴,有2个被命名为Mary。

我们把女性Mary在1880年对应的出生人数提取出来,然后再把1880年所有女性出生人数提取出来,把两者相除,就得到了(Mary,女,1880年)的百分比。

如果要把所有的姓名百分比计算出来,那么我们可以先把数据按年份和性别分类,然后再用apply方法对所有分类数据进行计算。

把数据按年份和性别分类:

by_year_gender=data.groupby(["Year", "Gender"])

定义apply方法所需的函数(由于该函数比较简单,因此这里直接用lambda函数):

lambda x: x["Birth"]/x["Birth"].sum()

把函数用于分类数据:

percentage=by_year_gender.apply(lambda x: x["Birth"]/x["Birth"].sum())

percentage的前10行显示如下:

Year  Gender
1880 F 0 0.077643
1 0.028618
2 0.022013
3 0.021309
4 0.019188
5 0.017342
6 0.016177
7 0.015540
8 0.014507
9 0.014155
Name: Birth, dtype: float64

由于percentage的索引和data相同,因此可以直接在data文件上增加一列Percentage:

data["Percentage"]=percentage.values

现在data的前10行如下:

        Name Gender  Birth  Year  Percentage
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

书中使用的是另外一种方法,在定义apply方法所需的函数时,直接在分类数据上加入计算出的百分比数据,这样apply方法可以在保证原数据的前提下把分组后计算出的百分比数据与原数据合并:

def add_percent(group):
birth=group["Birth"]
sum=group["Birth"].sum()
group["Percentage"]=birth/sum
return group data=by_year_gender.apply(add_percent)

但是,我们怎么确保各组百分比相加之和等于1呢?这时就需要做一个合理性检查(sanity check)。用numpy的allclose方法来查看每组百分比之和是否接近1:

import numpy as np
print(np.allclose(data.groupby(["Year","Gender"])["Percentage"].sum(),1))

结果显示为True。

在上面的分析过程中,我们发现这个数据文件比较大,为了加速分析进程,我们在这里提取每组(年份+性别)人数最多的前1000名的姓名进行分析:

by_year_gender=data.groupby(["Year", "Gender"])

pieces=[]
for year,group in by_year_gender:
pieces.append(group.sort_values(by="Birth", ascending=False)[:1000])
top1000=pd.concat(pieces, ignore_index=True)

后续的分析都将针对这个top1000数据集。

接下来让我们选取John,Harry,Mary,Marilyn这几个常见的名字,看一下它们各年份在同性别中占比分别是多少(时间趋势),并用折线图画出来。

fig,ax=plt.subplots(4,1,figsize=(8,8))

pivot=pd.pivot_table(top1000, values="Percentage", index=["Year", "Gender"], columns="Name")

for i, name, gender in zip(range(5),["John", "Harry", "Mary", "Marilyn"],["M","M","F","F"]):
ax[i].plot(range(1880,2011),pivot["{}".format(name)].unstack()["{}".format(gender)],label="{}".format(name))
ax[i].legend() plt.show()

首先,因为选取了4个名字,所以我们把图像分成4个分图。然后,按照年份和性别分组,显示各个名字的百分比,用名字作为列,这样可以很方便地选取数据。最后画图。

图像如下:

书上用的是这几个名字各年份的总出生人数:

fig,ax=plt.subplots(4,1,figsize=(8,8))

pivot=pd.pivot_table(top1000, values="Birth", index="Year", columns="Name", aggfunc="sum")

for i, name in zip(range(5),["John", "Harry", "Mary", "Marilyn"]):
ax[i].plot(range(1880,2011),pivot["{}".format(name)],label="{}".format(name))
ax[i].legend() plt.show()

总之,看起来常见的名字越来越不受欢迎了。那么到底是不是这样呢?让我们来验证一下。

把提取出来的排名前1000的名字每年的总占比画出来,如果总占比越来越少,那就说明使用其他名字的比例越来越高(名字越来越多样化)。

import numpy as np

total_percentage=pd.pivot_table(top1000, values="Percentage", index="Year", columns="Gender", aggfunc="sum")

ax.plot(range(1880,2011),total_percentage["F"], label="Female")
ax.plot(range(1880,2011),total_percentage["M"], label="Male")
ax.set_yticks(np.linspace(0,1.2,13))
ax.set_xticks(range(1880,2011,10))
ax.set_xlim(1880,2010) ax.legend() plt.show()

还有一种办法是计算总出生人数前50%的不同姓名的数量,如果数量在增加,就说明名字越来越多样化。

这个数据不是那么容易弄出来,让我们先以2010年男宝宝的名字为例,来算一下。

boy2010=top1000[(top1000["Gender"]=="M") & (top1000["Year"]==2010)]
boy2010=boy2010.sort_values(by="Percentage", ascending=False) boy2010["Percent_Cumsum"]=boy2010["Percentage"].cumsum()
num=boy2010["Percent_Cumsum"].searchsorted(0.5)

首先,提取2010年性别为男的数据,然后按照百分比进行排序,接下来在百分比这一列上使用累计函数cumsum。那么哪里才是百分比累计到50%的地方呢?答案是用searchsorted方法。num显示为116,由于索引是从0开始,因此前117个名字占总出生人数的前50%。

接下来,我们把这一方法扩展到top1000的总体数据上。首先定义apply方法所需的函数,再用apply方法把函数用于整个数据。

def get_percentile_num(group,p=0.5):
group=group.sort_values(by="Percentage", ascending=False)
percent_cumsum=group["Percentage"].cumsum()
num=percent_cumsum.searchsorted(p)
return num+1 by_year_gender=top1000.groupby(["Year", "Gender"])
percentile_num=by_year_gender.apply(get_percentile_num) percentile_num=percentile_num.unstack()
percentile_num["F"]=percentile_num["F"].astype(int)
percentile_num["M"]=percentile_num["M"].astype(int)

做完以上这两步后再把数据展开,并转换数据类型。

现在percentile_num的前10行显示如下:

Gender   F   M
Year
1880 38 14
1881 38 14
1882 38 15
1883 39 15
1884 39 16
1885 40 16
1886 41 16
1887 41 17
1888 42 17
1889 43 18

接下来就可以画图了:

ax.plot(range(1880,2011),percentile_num["F"],label="Female")
ax.plot(range(1880,2011),percentile_num["M"],label="Male")
ax.legend() plt.show()

这两种方法都证明随着年份增加,名字越来越多样化了,而且女性的名字多样化程度更甚。

最后一个字母的变革:有研究表明,近百年来,男孩名字的最后一个字母在数量分布上发生了显著变化。那么到底是不是这样呢?

首先提取男孩的名字数据,并在此数据上增加一列:Last_Letter,显示名字的最后一个字母。然后生成一张透视表,按最后一个字母分类,显示每个字母每年在男性名字中的占比。最后,用柱形图画出来。

boy=top1000[top1000["Gender"]=="M"]

boy["Last_Letter"]=boy["Name"].apply(lambda x: x[-1])

letters=pd.pivot_table(boy, values="Percentage", index="Last_Letter", columns="Year", aggfunc="sum")

fig,ax=plt.subplots(figsize=(12,6))

i=0
for year in [1910,1960,2010]:
ax.bar([j+i for j in range(26)],letters[year],label="{}".format(year), width=-0.3, align="edge")
i+=0.3
ax.legend()
ax.set_xticks(range(26))
ax.set_xticklabels(letters.index.values) plt.show()

图像如下:

可以看出,最后一个字母为n的男名比例越来越高了。

现在选取尾字母为"d","n","y"的男孩名字的百分比,并绘制折线图,这样就可以看出其随时间变化的趋势了。

由于我们需要选取字母,因此将letters透视表进行转置,这样就变成了字母为列,年份为行。letters.T的前5行显示如下:

Last_Letter         a         b         c         d         e         f  \
Year
1880 0.006842 0.004516 0.003159 0.083010 0.121664 0.000932
1881 0.007613 0.004665 0.003285 0.083247 0.123139 0.000824
1882 0.006623 0.004407 0.003070 0.084900 0.127658 0.001056
1883 0.007139 0.004272 0.002858 0.084066 0.125831 0.001013
1884 0.007095 0.004247 0.002744 0.085639 0.126803 0.001145 Last_Letter g h i j ... q r \
Year ...
1880 0.001285 0.036554 0.001810 NaN ... NaN 0.067326
1881 0.001449 0.037380 0.002045 NaN ... NaN 0.072190
1882 0.001240 0.036688 0.001821 NaN ... NaN 0.070043
1883 0.001290 0.037465 0.001596 NaN ... NaN 0.071680
1884 0.001311 0.036978 0.001381 NaN ... NaN 0.078055 Last_Letter s t u v w x \
Year
1880 0.166735 0.062755 0.000181 0.000299 0.007629 0.002751
1881 0.162495 0.061818 0.000258 0.000179 0.007424 0.002650
1882 0.160265 0.062109 0.000088 0.000378 0.007653 0.003079
1883 0.158116 0.064359 0.000105 0.000421 0.007589 0.002657
1884 0.154327 0.060973 0.000087 0.000315 0.007217 0.002997 Last_Letter y z
Year
1880 0.075398 0.000262
1881 0.077451 0.000079
1882 0.076966 0.000229
1883 0.079096 0.000115
1884 0.079532 0.000236 [5 rows x 26 columns]

接下来用d,n,y的数据分别画折线图:

for letter in ["d","n","y"]:
ax.plot(letters.T.index.values, letters.T[letter], label=letter)
ax.set_xlim(1880,2010)
ax.legend() plt.show()

男名变女名,女名变男名

有的名字本来是男孩取用的多,但是随着时间的改变,渐渐变成了受女孩欢迎的名字。我们以Lesly相关的名字为例,来看一看这个变化。

首先把Lesly相关的名字提取出来,看看有哪些:

all_names=top1000["Name"].unique()

mask=["lesl" in name.lower() for name in all_names]

lesly_like=all_names[mask]
['Leslie' 'Lesley' 'Leslee' 'Lesli' 'Lesly']

这里提取了lesl开头的名字,一共有5种。

在top1000数据集里提取这5种名字的数据,然后做一个透视表。

filtered=top1000[top1000["Name"].isin (lesly_like)]

table=pd.pivot_table(filtered, values="Birth", index="Year", columns="Gender", aggfunc="sum")

在透视表里增加一列:当年男女出生人数的总和。

table["Sum"]=table["F"]+table["M"]

现在透视表的前5行如下:

Gender     F      M    Sum
Year
1880 8.0 79.0 87.0
1881 11.0 92.0 103.0
1882 9.0 128.0 137.0
1883 7.0 125.0 132.0
1884 15.0 125.0 140.0

按每年男女出生人数比例画图:

ax.plot(table.index.values, table["F"]/table["Sum"], "-", label="Female")
ax.plot(table.index.values, table["M"]/table["Sum"], "--", label="Male") ax.legend() plt.show()

图像如下:

由此可以看出,Lesly相关的名字由男名变成了女名。

数据分析---《Python for Data Analysis》学习笔记【03】的更多相关文章

  1. Python for Data Analysis 学习心得(一) - numpy介绍

    一.简介 Python for Data Analysis这本书的特点是将numpy和pandas这两个工具介绍的很详细,这两个工具是使用Python做数据分析非常重要的一环,numpy主要是做矩阵的 ...

  2. Python for Data Analysis 学习心得(四) - 数据清洗、接合

    一.文字处理 之前在练习爬虫时,常常爬了一堆乱七八糟的字符下来,当时就有找网络上一些清洗数据的方式,这边pandas也有提供一些,可以参考使用看看.下面为两个比较常见的指令,往往会搭配使用. spli ...

  3. Python for Data Analysis 学习心得(三) - 文件读写和数据预处理

    一.Pandas文件读写 pandas很核心的一个功能就是数据读取.导入,pandas支援大部分主流的数据储存格式,并在导入的时候可以做筛选.预处理.在读取数据时的选项有超过50个参数,可见panda ...

  4. Python for Data Analysis 学习心得(二) - pandas介绍

    一.pandas介绍 本篇程序上篇内容,在numpy下面继续介绍pandas,本书的作者是pandas的作者之一.pandas是非常好用的数据预处理工具,pandas下面有两个数据结构,分别为Seri ...

  5. 数据分析---《Python for Data Analysis》学习笔记【04】

    <Python for Data Analysis>一书由Wes Mckinney所著,中文译名是<利用Python进行数据分析>.这里记录一下学习过程,其中有些方法和书中不同 ...

  6. 数据分析---《Python for Data Analysis》学习笔记【02】

    <Python for Data Analysis>一书由Wes Mckinney所著,中文译名是<利用Python进行数据分析>.这里记录一下学习过程,其中有些方法和书中不同 ...

  7. 数据分析---《Python for Data Analysis》学习笔记【01】

    <Python for Data Analysis>一书由Wes Mckinney所著,中文译名是<利用Python进行数据分析>.这里记录一下学习过程,其中有些方法和书中不同 ...

  8. 学习笔记之Python for Data Analysis

    Python for Data Analysis, 2nd Edition https://www.safaribooksonline.com/library/view/python-for-data ...

  9. 1.2 Why Python for Data Analysis(为什么使用Python做数据分析)

    1.2 Why Python for Data Analysis?(为什么使用Python做数据分析) 这节我就不进行过多介绍了,Python近几年的发展势头是有目共睹的,尤其是在科学计算,数据处理, ...

随机推荐

  1. DIV中文字换行显示

    居然第一次遇到这种问题,还想了半天到底是怎么回事,为什么明明div设置宽度了,里面的文字超过宽度后居然不换行. (1)word-break属性,可以让浏览器实现在任意位置换行. normal:使用浏览 ...

  2. 基础环境系列:Apache2.4.37

    一.安装 进入官网http://www.apache.org/,滑至最下方,排名第一的HTTP Server就是我们需要的. 当前时间的最新版本是2.4.37.呃……并没有msi版本,我们选择最后一个 ...

  3. angular应用容器化部署

    angular 应用容器化部署 Intro 我自己有做一个个人主页,虽然效果不怎么样(不懂设计的典型程序猿...),但是记录了我对于前端框架及工具的一些实践, 从开始只有一个 angularjs 制作 ...

  4. Redis持久化的方式

    Redis小知识: redis是键值对的数据库,有5中主要数据类型: 字符串类型(string),散列类型(hash),列表类型(list),集合类型(set),有序集合类型(zset) Redis持 ...

  5. Delphi Record To Stream

    type TUserInfo = record sUserId,sUserName:String; iUserCount:integer; end; procedure TForm1.Button1C ...

  6. 一道CTF题引发的思考——SSI注入

    题目地址:http://210.32.4.22/index.php 一开始我一直考虑的用<!--#include file="文件"-->的格式进行读取文件,但是一直不 ...

  7. Github如何提交修改的代码以及更新到最新版本

    最近有人问我,Github上如何把修改fork到的代码提交到原版本上去,以及如何更新到最新的版本.只针对初学者,大神的话勿喷. 首先说第一个问题. 进入到你修改的某个repository里面(以本人的 ...

  8. #019 还未搞明白的C语言问题

    吐槽一下作业系统 自己电脑上跑的好好地到他这里就给我算错了.... 是我的问题还是系统的问题?????摸不着头脑 总分 12 从键盘任意输入某班30个学生的成绩(成绩类型为整型),保存到数组中,并输出 ...

  9. 一个数据源demo

    前言 我们重复造轮子,不是为了证明我们比那些造轮子的人牛逼,而是明白那些造轮子的人有多牛逼. JDBC介绍 在JDBC中,我们可以通过DriverManager.getConnection()创建(而 ...

  10. nysql报错1136

    报错信息:> 1136 - Column count doesn't match value count at row 1 代码:insert into class(caption) value ...