客至汲泉烹茶, 抚琴听者知音

利用python进行数据收集、整理、分析和绘图的全过程

这是github上的一个项目,作为数据分析基础入门是非常好的,于是我把它译为中文,希望对大家有所帮助。

原文地址

代码均已在本地测试通过。

要求

在此存储库中,我将记录收集数据,处理数据并进行可视化的完整过程。

该项目中使用的数据集是国家数据-社会保障卡应用中心的婴儿名字,其中包括1880年至2018年的记录。

需要安装python3与以下库:

  • Requests - 下载数据集.
  • pandas - 进行数据分析.
  • NumPy - 矩阵运算.
  • Matplotlib - 绘图.
  • seaborn - 美化图.

获取数据

import requests
url = "https://www.ssa.gov/oact/babynames/names.zip"

with requests.get(url) as response:
    with open("names.zip", "wb") as temp_file:
        temp_file.write(response.content)
提示:如果因为网速等问题下载失败,可手动复制链接到浏览器中下载。

这就将数据的zip包下载到我们电脑中了,它包括了各年份数据的txt文件与一个说明pdf文档,我们需要做的是读取txt文件,进行处理并保存到csv中。这个新的csv文件将成为我们的数据集,并将包含4个字段(年份,名称,性别和数量)

import csv
from zipfile import ZipFile
# 这两个都是内置模块,无需安装
data_list = [["year", "name", "gender", "count"]]
# 此列表将保存我们的所有数据。我们使用标题行对其进行初始化。
with ZipFile("names.zip") as temp_zip: # 读取zip对象
    for file_name in temp_zip.namelist(): # 读取文件列表            
        if ".txt" in file_name: # 只读取txt文档            
            with temp_zip.open(file_name) as temp_file: # 从压缩包中读取这个txt                
                for line in temp_file.read().decode("utf-8").splitlines():
                # 文件以二进制形式打开,我们使用utf-8对其进行解码,以便可以将其作为字符串进行操作。
                    # 准备好我们所需的数据字段并将其添加到列表中.
                    line_chunks = line.split(",")
                    year = file_name[3:7]
                    name = line_chunks[0]
                    gender = line_chunks[1]
                    count = line_chunks[2]

                    data_list.append([year, name, gender, count])
# 保存成csv
csv.writer(open("data.csv", "w", newline="",
                encoding="utf-8")).writerows(data_list)

首先我们声明一个data_list,它是一个二维数组。目的是为了稍后我们可以用csv.writer.writerows()方法保存到csv中。(我更喜欢用writerows()而不是writerow(),因为它更快)

接下来的步骤都有注释,就不再赘述了。

数据探索(Data Exploration)

基础信息

首先导入相关库

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns

接着我们使用pandas.read_csv()方法读取csv

df = pd.read_csv("data.csv")
# 由于我们的数据集非常简单,因此我们不需要设置任何特殊标志或额外参数,默认值就足够了。

让我们从经典的df.head()df.tail()方法开始,他们分别能查看数据集前几行和后几行。

df.head() # 前五行
>>> year       name  gender  count
0  1880       Mary      F   7065
1  1880       Anna      F   2604
2  1880       Emma      F   2003
3  1880  Elizabeth      F   1939
4  1880     Minnie      F   1746
df.tail() # 后五行
>>>      year   name  gender   count
1957041  2018  Zylas      M      5
1957042  2018  Zyran      M      5
1957043  2018  Zyrie      M      5
1957044  2018  Zyron      M      5
1957045  2018  Zzyzx      M      5

这些样本告诉我们以下信息:

  • 有4列(年份,名称,性别和计数)。
  • 有1,957,046行。
  • 行按年份排序。
  • 女性记录显示在男性记录之前。
  • 2018年,至少有5个父母将其儿子命名为``Zzyzx''。

唯一名称

df["name"].nunique()  # 拥有独一无二名字的人有多少
df[df["gender"] == "M"]["name"].nunique() # 女性拥有独一无二名字的人有多少
df[df["gender"] == "F"]["name"].nunique() # 男性拥有独一无二名字的人有多少

# 性别中立的人有多少拥有独一无二的名字
both_df = df.pivot_table(index="name", columns="gender", values="count", aggfunc=np.sum).dropna()
both_df.index.nunique()

可以看出,数据集中有98,400个人拥有唯一名称。其中,男性41,475名,女性67,698名,性别中立10,773名。

十大常用名字

为了获得前10个最常用的男性和女性名字,我们将首先用dataframe按性别过滤。

有了特定性别dataframe后,我们将只选择2个字段,即姓名和人数。然后在name字段上使用groupby()方法,并使用sum()汇总结果。

最后,我们将对count字段上的值进行降序排序,并使用head(10)方法获得前10个结果。

df = df[df["gender"] == "M"]
df = df[["name", "count"]]
df = df.groupby("name")
df = df.sum()
df = df.sort_values("count", ascending=False)
df.head(10)

>>>           
name      count           
James    5164280
John     5124817
Robert   4820129
Michael  4362731
William  4117369
David    3621322
Joseph   2613304
Richard  2565301
Charles  2392779
Thomas   2311849

上面的代码可以写到一起,即:

df[df["gender"] == "M"][["name", "count"]].groupby("name").sum().sort_values("count", ascending=False).head(10)

当然,把"gender"改成"F"就可以找女性十大常用名了。

前二十大性别中立者常用名

这有点挑战,首先我们需要旋转dataframe,以便名称成为索引,性别成为列,所有计数的总和(每个名称,每个性别)将成为我们的值。

我们将分步骤进行。首先,我们旋转表并删除值为0的行。即名称不在男性或女性中的行。

df = df.pivot_table(index="name", columns="gender", values="count", aggfunc=np.sum).dropna()

会输出类似的数据:

gender         F       M
name                    
Aaden        5.0  4828.0
Aadi        16.0   851.0
Aadyn       16.0   516.0
Aalijah    149.0   212.0
Aaliyah  87442.0    96.0
         ...     ...

有了这种形状的数据,我们现在知道每个姓名每个性别有多少条记录。

现在,我们仅考虑那些至少具有50,000个性别记录的名称。

df = df[(df["M"] >= 50000) & (df["F"] >= 50000)]
df.head(20)

注意:原本我只使用前10个名字,但由于只有20个符合我们标准的名字,我将其推到了20个。

姓名记录最高和最低的年份

现在,我们将知道哪个年份的记录之和最高和最低。

both_df = df.groupby("year").sum()
male_df = df[df["gender"] == "M"].groupby("year").sum()
female_df = df[df["gender"] == "F"].groupby("year").sum()

# 组合最小值(年份和计数)
both_df.min()["count"]
both_df.idxmin()["count"]

# 男性最小值(年份和计数)
male_df.min()["count"]
male_df.idxmin()["count"]

# Female Min (count and year)
female_df.min()["count"]
female_df.idxmin()["count"]

# Combined Max (count and year)
both_df.max()["count"]
both_df.idxmax()["count"]

# Male Max (count and year)
male_df.max()["count"]
male_df.idxmax()["count"]

# Female Max (count and year)
female_df.max()["count"]
female_df.idxmax()["count"]

绘图

在此项目中,我们仅使用线绘图,这对于显示值随时间的变化非常有帮助。

首先要做的是设置一些自定义颜色,这些颜色将全局应用于每个图。

# 这些参数生成带有淡紫色的图。
sns.set(style="ticks",
        rc={
            "figure.figsize": [12, 7],
            "text.color": "white",
            "axes.labelcolor": "white",
            "axes.edgecolor": "white",
            "xtick.color": "white",
            "ytick.color": "white",
            "axes.facecolor": "#443941",
            "figure.facecolor": "#443941"}
        )

声明样式后,我们就可以绘制数据了。为了效率,接下来我会使用单线,但是不用担心,我会解释每条线的作用。

按年份计数

我们第一个线绘图将展示历年姓名记录数据的变化。

首先,为男性、女性、组合创建新的dataframes

both_df = df.groupby("year").sum()
male_df = df[df["gender"] == "M"].groupby("year").sum()
female_df = df[df["gender"] == "F"].groupby("year").sum()

我们直接绘制dataframes。索引为x轴,总数为y轴。

plt.plot(both_df, label="Both", color="yellow")
plt.plot(male_df, label="Male", color="lightblue")
plt.plot(female_df, label="Female", color="pink")

设置y轴的间距为500,000,首先我们格式化标签的数据,然后使用实际数据作为间隔。

yticks_labels = ["{:,}".format(i) for i in range(0, 4500000+1, 500000)]
plt.yticks(np.arange(0, 4500000+1, 500000), yticks_labels)

添加一些其他设置项

plt.legend()
plt.grid(False)
plt.xlabel("Year")
plt.ylabel("Records Count")
plt.title("Records per Year")
plt.show()

最受欢迎名字的增长情况

首先,我们合并男性和女性的值,然后旋转表,让名称是索引,年份是列。我们还用零填充缺失值。

pivoted_df = df.pivot_table(
index="name", columns="year", values="count", aggfunc=np.sum).fillna(0)

然后按年份计算每个名字的百分比。

percentage_df = pivoted_df / pivoted_df.sum() * 100

增加新的一列作为百分比之和

percentage_df["total"] = percentage_df.sum(axis=1)

我们对dataframe进行排序以检查哪些是最高值并将进行切片。之后,我们删除total列,因为将不再使用它。

sorted_df = percentage_df.sort_values(
by="total", ascending=False).drop("total", axis=1)[0:10]

翻转轴,以便更轻易地绘图

transposed_df = sorted_df.transpose()

我们通过使用列名作为标签和Y轴来单独地绘制每个名字。

for name in transposed_df.columns.tolist():
    plt.plot(transposed_df.index, transposed_df[name], label=name)

设置y轴间距为0.5%

yticks_labels = ["{}%".format(i) for i in np.arange(0, 5.5, 0.5)]
plt.yticks(np.arange(0, 5.5, 0.5), yticks_labels)

增加最后的自定义项

plt.legend()
plt.grid(False)
plt.xlabel("Year")
plt.ylabel("Percentage by Year")
plt.title("Top 10 Names Growth")
plt.show()

十大热门名字

我们将获得过去10年(2008-2018)的最热门的十个名字变化趋势

和上面的思路类似,直接上代码

# 首先要做的是删除所有早于2008年的记录。
filtered_df = df[df["year"] >= 2008]
    
# 然后,我们合并男性女性值,并对表进行数据透视,因此名称是我们的索引,年份是我们的列。我们还用零填充缺失值。
pivoted_df = filtered_df.pivot_table(
    index="name", columns="year", values="count", aggfunc=np.sum).fillna(0)

# 然后我们按年份计算每个名称的百分比。
percentage_df = pivoted_df / pivoted_df.sum() * 100

# 我们添加了一个新列来表示百分比之和。
percentage_df["total"] = percentage_df.sum(axis=1)

# 我们对dataframe进行排序以检查哪些是最高值并将进行切片。之后,我们删除total列,因为将不再使用它。
sorted_df = percentage_df.sort_values(
    by="total", ascending=False).drop("total", axis=1)[0:10]

# 翻转轴,以便更轻松地绘制数据框。
transposed_df = sorted_df.transpose()

# 我们通过使用列名作为标签和Y轴来单独地绘制每个名字。
for name in transposed_df.columns.tolist():
    plt.plot(transposed_df.index, transposed_df[name], label=name)

# Y轴0.5%的间距
yticks_labels = ["{:.2f}%".format(i) for i in np.arange(0.3, 0.7, 0.05)]
plt.yticks(np.arange(0.3, 0.7, 0.05), yticks_labels)

# 从2008年到2018年,我们将x轴的间距设为1
xticks_labels = ["{}".format(i) for i in range(2008, 2618+1, 1)]
plt.xticks(np.arange(2008, 2018+1, 1), xticks_labels)

# 最后设置
plt.legend()
plt.grid(False)
plt.xlabel("Year")
plt.ylabel("Percentage by Year")
plt.title("Top 10 Trending Names")
plt.show()

添加新评论