跳到主要内容
Python 数据分析实战指南:基于 Pandas 的四个经典案例 | 极客日志
Python AI 算法
Python 数据分析实战指南:基于 Pandas 的四个经典案例 综述由AI生成 使用 Python 的 Pandas 库进行数据分析的基础知识与实战案例。涵盖了 Numpy、Matplotlib 等工具的安装与导入,详细讲解了 Series、DataFrame 等数据结构及 read_table、merge、iloc、pivot_table、groupby 等核心函数。通过 MovieLens 电影评分、美国婴儿姓名、美国农业部食品数据库三个实际数据集,演示了数据读取、清洗、合并、透视表分析及可视化可视化的完整流程。内容适合希望掌握 Python 数据处理技能的开发者参考。
随缘 发布于 2025/2/7 更新于 2026/5/28 17 浏览一、前期准备
进行 Python 数据分析主要依赖三个核心库:Numpy、Pandas 和 Matplotlib;开发工具推荐使用 Jupyter Notebook。首先确保导入这两个包。
import numpy as np
import pandas as pd
二、基础知识
Pandas 提供了三种主要数据结构:Series、DataFrame 和 Panel。Series 类似于一维数组;DataFrame 是类似表格的二维数组;Panel 可以视为 Excel 的多表单 Sheet(注:在较新版本中 Panel 已不推荐使用,建议用 MultiIndex DataFrame 替代)。
read_table
用于读取 csv、excel、dat 等文本文件。常用参数包括 filepath_or_buffer(文件路径)、sep(分隔符)、header(表头行号)、names(列名列表)等。
read_table(filepath_or_buffer, sep=False , delimiter=None , header='infer' , names=None , index_col=None , usecols=None , squeeze=False , prefix=None , mangle_dupe_cols=True , dtype=None , engine=None , converters=None , true_values=None , false_values=None , skipinitialspace=False , skiprows=None , skipfooter=0 , nrows=None , na_values=None , keep_default_na=True , na_filter=True , verbose=False , skip_blank_lines=True , parse_dates=False , infer_datetime_format=False , keep_date_col=False , date_parser=None , dayfirst=False , iterator=False , chunksize= , compression= , thousands= , decimal= , lineterminator= , quotechar= , quoting= , doublequote= , escapechar= , comment= , encoding= , dialect= , tupleize_cols= , error_bad_lines= , warn_bad_lines= , delim_whitespace= , low_memory= , memory_map= , float_precision= )
None
'infer'
None
b'.'
None
'"'
0
True
None
None
None
None
None
True
True
False
True
False
None
连接两个 DataFrame 并返回连接之后的 DataFrame。支持内连接(inner)、外连接(outer)、左连接(left)、右连接(right)等多种模式。
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 , indicator=False , validate=None )
通过行号和列号来取数据。例如 df.iloc[0] 取第一行数据,df.iloc[:, 0] 取第一列数据。
通过使用 pandas.pivot_table() 函数,可以实现与电子表格软件(例如 Excel)的数据透视表功能相同的处理,常用于聚合统计。
和 SQL 中的分组类似,Pandas 中的 groupby 函数也是先将 DataFrame 按照某个字段进行拆分,将相同属性分为一组;然后对拆分后的各组执行相应的转换操作;最后输出汇总转换后的各组结果。
三、具体案例 数据分析的标准步骤通常包括:1.提出问题 2.理解数据 3.数据清洗 4.构建模型 5.数据可视化。
3.1 MovieLens 1M 数据集 GroupLens 实验室提供了一些从 MovieLens 用户那里收集的 20 世纪 90 年代末到 21 世纪初的电影评分数据的集合。该数据集提供了电影的评分、流派、年份和观众数据(年龄、邮编、性别、职业)。MovieLens 1M 数据集包含 6000 个用户对 4000 部电影的 100 万个评分。数据分布在三个表格之中:分别包含评分、用户信息和电影信息。
unames = ["user_id" , "gender" , "age" , "occupation" , "zip" ]
users = pd.read_table("datasets/movielens/users.dat" , sep="::" ,
header=None , names=unames, engine="python" )
rnames = ["user_id" , "movie_id" , "rating" , "timestamp" ]
ratings = pd.read_table("datasets/movielens/ratings.dat" , sep="::" ,
header=None , names=rnames, engine="python" )
mnames = ["movie_id" , "title" , "genres" ]
movies = pd.read_table("datasets/movielens/movies.dat" , sep="::" ,
header=None , names=mnames, engine="python" )
首先读取 users.dat、ratings.dat、movies.dat 三个文件,并将它们存储在不同的 DataFrame 中,分别命名为 users、ratings、movies。
users.head(5 )
ratings.head(5 )
movies.head(5 )
分别输出三个 DataFrame 的前五行,以查看数据结构。
data = pd.merge(pd.merge(ratings, users), movies)
data.iloc[0 ]
使用 merge 函数将 ratings、users 和 movies 进行合并,保留了三个 DataFrame 中所有的数据,并将它们之间重复的数据和行进行合并。合并生成名为 data 的新 DataFrame,并输出整个数据以及读取第一行数据。
mean_ratings = data.pivot_table("rating" , index="title" ,
columns="gender" , aggfunc="mean" )
mean_ratings.head(5 )
使用 pivot_table 函数实现数据透视表功能,对 rating 中 title 列求均值,columns 参数就是用来显示字符型数据的,显示性别数据。求均值生成名为 mean_ratings 的新 DataFrame,并读取输出前五行数据。
ratings_by_title = data.groupby("title" ).size()
ratings_by_title.head()
active_titles = ratings_by_title.index[ratings_by_title >= 250 ]
active_titles
使用 groupby 函数对 data 这一 DataFrame 按照电影名称 title 分组,并计算每个电影标题对应的评分数量。第二行代码显示每个电影标题对应的评分数量。第三四行代码统计对应评分数量大于 250 的电影标题将其定义为 active_titles 并输出。
mean_ratings = mean_ratings.loc[active_titles]
mean_ratings
读取 mean_ratings 中评分数量大于 250 的电影标题对应的数据并输出。
mean_ratings = mean_ratings.rename(index={"Seven Samurai (The Magnificent Seven) (Shichinin no samurai) (1954)" :
"Seven Samurai (Shichinin no samurai) (1954)" })
使用 rename 函数将 mean_ratings 中 Seven Samurai (The Magnificent Seven) (Shichinin no samurai) (1954) 重新更改为 Seven Samurai (Shichinin no samurai) (1954),统一命名格式。
top_female_ratings = mean_ratings.sort_values("F" , ascending=False )
top_female_ratings.head()
根据女性的评分使用排序函数对 mean_ratings 进行降序排序并输出,找出女性评分最高的电影。
mean_ratings["diff" ] = mean_ratings["M" ] - mean_ratings["F" ]
用 mean_ratings 中男性评分减去女性评分计算出男女评分差异 diff。
sorted_by_diff = mean_ratings.sort_values("diff" )
sorted_by_diff.head()
根据 diff 列的值使用排序函数对 mean_ratings 进行升序排序并输出。
sorted_by_diff[::-1 ].head()
使用切片操作对 diff 进行逆序排序,并输出,查看男性评分远高于女性评分的电影。
rating_std_by_title = data.groupby("title" )["rating" ].std()
rating_std_by_title = rating_std_by_title.loc[active_titles]
rating_std_by_title.head()
std 函数用于表示标准差。对电影标题 title 根据评分标准差分组。并读取活跃标题(评分数量大于 250 的电影标题)的标准差输出。
rating_std_by_title.sort_values(ascending=False )[:10 ]
根据评分标准差进行降序排序并读取前十行,也即输出评分标准差最大的十个电影标题,这些电影可能争议较大。
movies["genres" ].head()
movies["genres" ].head().str .split("|" )
movies["genre" ] = movies.pop("genres" ).str .split("|" )
movies.head()
读取电影中 genres 列数据,并通过 | 分隔开。将分割后的数据命名为 genre 列,原数据列 genres 删除。
movies_exploded = movies.explode("genre" )
movies_exploded[:10 ]
使用 explode 函数将 genre 列中分割的数据展开成单独的几列数据并记为 movies_exploded 这个新 DataFrame,输出前十行数据。
ratings_with_genre = pd.merge(pd.merge(movies_exploded, ratings), users)
ratings_with_genre.iloc[0 ]
genre_ratings = (ratings_with_genre.groupby(["genre" , "age" ])
["rating" ].mean()
.unstack("age" ))
genre_ratings[:10 ]
将 movies_exploded、ratings、users 这三个合并起来生成一个新 DataFrame,并读取第一行数据。按照 genre 和 age 进行分组,并计算每个组评分的平均值,使用 unstack 函数将结果重塑为以 age 为列索引的形式。
3.2 美国 1880-2010 年的婴儿名字 美国社会保障局(SSA)提供了从 1880 年至现在的婴儿姓名频率的数据。可以使用这些数据做很多事情:根据给定的名字对婴儿名字随时间的比例进行可视化,确定一个名字的相对排位,确定每年最受欢迎的名字,或者流行程度最高或最低的名字。
names1880 = pd.read_csv("datasets/babynames/yob1880.txt" ,
names=["name" , "sex" , "births" ])
names1880
读取名为'yob1880.txt'文件,并将其列名设为 name、sex、births。
names1880.groupby("sex" )["births" ].sum ()
pieces = []
for year in range (1880 , 2011 ):
path = f"datasets/babynames/yob{year} .txt"
frame = pd.read_csv(path, names=["name" , "sex" , "births" ])
frame["year" ] = year
pieces.append(frame)
names = pd.concat(pieces, ignore_index=True )
names
提取从数据集中读取 1880-2011 年间的数据并生成 names 这个 DataFrame。
total_births = names.pivot_table("births" , index="year" ,
columns="sex" , aggfunc=sum )
total_births.tail()
total_births.plot(title="Total births by sex and year" )
使用 pivot_table 函数以 births 和 sex 分组的出生数总和,并显示最后几行。绘制一个标题为 Total births by sex and year 的折线图。
def add_prop (group ):
group["prop" ] = group["births" ] / group["births" ].sum ()
return group
names = names.groupby(["year" , "sex" ], group_keys=False ).apply(add_prop)
names
定义一个增加组的函数 add_prop,表示每个名字在出生年份和性别组中的比例,每个名字的出生率。对 names 按照年份和性别分组,并对每组应用 add_prop 函数。
names.groupby(["year" , "sex" ])["prop" ].sum ()
通过年份和性别分组,并计算对每组中的每个名字比例的总和,验证归一化是否正确。
def get_top1000 (group ):
return group.sort_values("births" , ascending=False )[:1000 ]
grouped = names.groupby(["year" , "sex" ])
top1000 = grouped.apply(get_top1000)
top1000.head()
定义一个 get_top1000 的函数,该函数根据 births 进行降序排序,并取前 1000 行,也即 births 值最大的前 1000。根据年份和性别分组,并对每个分组应用 get_top1000 函数。
top1000 = top1000.reset_index(drop=True )
top1000.head()
使用 reset_index() 函数对 top1000 DataFrame 进行重置索引,并丢弃原始索引。设置 drop=True 可以移除原始索引列,以便在重置索引后不保留它。
boys = top1000[top1000["sex" ] == "M" ]
girls = top1000[top1000["sex" ] == "F" ]
total_births = top1000.pivot_table("births" , index="year" ,
columns="name" ,
aggfunc=sum )
total_births.info()
subset = total_births[["John" , "Harry" , "Mary" , "Marilyn" ]]
subset.plot(subplots=True , figsize=(12 , 10 ),
title="Number of births per year" )
根据性别将 top1000 的值分为 boys 和 girls 两个数据集。并对 births 进行数据透视。使用 info() 方法打印出 total_births 的全部数据,并选择 John、Harry、Mary、Marilyn 四个名字绘制标题为 Number of births per year 的折线图。
plt.figure()
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 ))
对 prop 进行数据透视图,绘制标题为 Sum of table1000.prop by year and sex 的折线图。
df = boys[boys["year" ] == 2010 ]
df
prop_cumsum = df["prop" ].sort_values(ascending=False ).cumsum()
prop_cumsum[:10 ]
prop_cumsum.searchsorted(0.5 )
对 2010 年男孩出生人数表中 prop 值进行降序排序并计算累计和,并提取前 10 行,使用 searchsorted() 方法找到累计和达到 0.5 时的索引位置。
df = boys[boys.year == 1900 ]
in1900 = df.sort_values("prop" , ascending=False ).prop.cumsum()
in1900.searchsorted(0.5 ) + 1
得到 1900 年男孩出生人数表,对表中 prop 值进行降序排序并计算累计和,searchsorted() 方法找到累计和达到 0.5 时的后一个索引位置。
def get_quantile_count (group, q=0.5 ):
group = group.sort_values("prop" , ascending=False )
return group.prop.cumsum().searchsorted(q) + 1
diversity = top1000.groupby(["year" , "sex" ]).apply(get_quantile_count)
diversity = diversity.unstack()
fig = plt.figure()
diversity.head()
diversity.plot(title="Number of popular names in top 50%" )
定义一个 get_quantile_count 函数,对 prop 值进行降序排序并计算累计和,searchsorted() 方法找到累计和达到 0.5 时的后一个索引位置。根据年份和性别分组,并对每组应用 get_quantile_count 函数,得到 diversity 这个新 DataFrame,并绘制标题为 Number of popular names in top 50% 的折线图。
def get_last_letter (x ):
return 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.head()
定义一个返回字符串最后一个字母的函数。使用 map 函数对 names 中每一个名字提取最后一个字母。进行数据透视。展示 1910、1960、2010 年的数据。
subtable.sum ()
letter_prop = subtable / subtable.sum ()
letter_prop
展示每个年份和性别组合中每个名字的总和,以及占比。
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" )
letter_prop["F" ].plot(kind="bar" , rot=0 , ax=axes[1 ], title="Female" ,
legend=False )
导入 matplotlib 包分别以男生和女生绘制两幅柱状图。
letter_prop = table / table.sum ()
dny_ts = letter_prop.loc[["d" , "n" , "y" ], "M" ].T
dny_ts.head()
统计最后男生中名字最后一个字母为 d、n、y 的比例。
all_names = pd.Series(top1000["name" ].unique())
lesley_like = all_names[all_names.str .contains("Lesl" )]
lesley_like
从 top1000 DataFrame 的'name'列获取唯一的姓名,并将结果存储在 all_names 变量中。选择 all_names 中包含'Lesl'的姓名,并将结果赋值给 lesley_like 变量。显示 lesley_like Series,即包含以'Lesl'开头的姓名。
filtered = top1000[top1000["name" ].isin(lesley_like)]
filtered.groupby("name" )["births" ].sum ()
根据 top1000 DataFrame 中的'name'列与 lesley_like 中的姓名进行匹配,筛选出匹配的行数据,并将结果赋值给 filtered 变量。对 filtered DataFrame 按姓名进行分组,计算每个姓名的出生人数总和,并显示结果。
table = filtered.pivot_table("births" , index="year" ,
columns="sex" , aggfunc="sum" )
table = table.div(table.sum (axis="columns" ), axis="index" )
table.tail()
根据年份和性别对 filtered 进行透视,计算每个年份和性别的出生人数总和,并将结果存储在 table 变量中。对 table 进行归一化,即每行的总和作为除数,计算每个年份和性别的归一化比例。table 归一化后最后几行的结果。
fig = plt.figure()
table.plot(style={"M" : "k-" , "F" : "k--" })
3.3 美国农业部食品数据库 美国农业部提供了食物营养信息数据库。每种事务都有一些识别属性以及两份营养元素和营养比例的列表。这种形式的数据不适合分析,所以需要做一些工作将数据转换成更好的形式。
import json
db = json.load(open ("datasets/usda_food/database.json" ))
len (db)
db[0 ].keys()
db[0 ]["nutrients" ][0 ]
nutrients = pd.DataFrame(db[0 ]["nutrients" ])
nutrients.head(7 )
获得 db 列表中索引为 0 的所有关键值。从 db 列表中索引为 0 的元素中获取键为'nutrients'的值的列表,并返回列表中的第一个元素。将 db 列表中索引为 0 的元素中的'nutrients'值转换为 Pandas DataFrame 对象。显示 nutrients DataFrame 的前 7 行数据。
info_keys = ["description" , "group" , "id" , "manufacturer" ]
info = pd.DataFrame(db, columns=info_keys)
info.head()
info.info()
包含要从数据库中提取的信息的键的列表。使用 info_keys 作为列名,创建包含 db 数据的 Pandas DataFrame 对象,并将其存储在 info 变量中。显示 info DataFrame 的前几行数据。显示 info DataFrame 的基本信息。
pd.value_counts(info["group" ])[:10 ]
从 DataFrame info 中选择了名为'group'的列,该列包含了食物的分组信息。对选定列中的每个唯一值进行计数,并返回计数结果。取计数结果中的前 10 个值,即返回出现次数最多的前 10 个分组。
nutrients = []
for rec in db:
fnuts = pd.DataFrame(rec["nutrients" ])
fnuts["id" ] = rec["id" ]
nutrients.append(fnuts)
nutrients = pd.concat(nutrients, ignore_index=True )
nutrients
创建一个空列表。定义一个函数为每个记录创建一个包含营养信息的 DataFrame 对象,添加一个名为'id'的列,将记录的 id 值赋给该列的每个元素,并将每个记录的营养信息 DataFrame 添加到 nutrients 列表中,将 nutrients 列表中的 DataFrame 对象合并为一个大的 DataFrame,并重新索引行号。
nutrients.duplicated().sum ()
nutrients = nutrients.drop_duplicates()
计算 duplicates 的总值,并将其赋值给 nutrients。
col_mapping = {"description" : "food" ,
"group" : "fgroup" }
info = info.rename(columns=col_mapping, copy=False )
info.info()
col_mapping = {"description" : "nutrient" ,
"group" : "nutgroup" }
nutrients = nutrients.rename(columns=col_mapping, copy=False )
nutrients
定义一个字典,里面有两个键值对。将其重命名为 info,并输出。定义另一个字典,将其重命名为 nutrients 并输出。
ndata = pd.merge(nutrients, info, on="id" )
ndata.info()
ndata.iloc[30000 ]
合并 nutrients 和 info,并读取第 30000 行数据。
fig = plt.figure()
result = ndata.groupby(["nutrient" , "fgroup" ])["value" ].quantile(0.5 )
result["Zinc, Zn" ].sort_values().plot(kind="barh" )
以 nutrient 和 fgroup 分组,并排序绘制柱状图。
by_nutrient = ndata.groupby(["nutgroup" , "nutrient" ])
def get_maximum (x ):
return x.loc[x.value.idxmax()]
max_foods = by_nutrient.apply(get_maximum)[["value" , "food" ]]
max_foods["food" ] = max_foods["food" ].str [:50 ]
max_foods.loc["Amino Acids" ]["food" ]
根据 nutgroup 和 nutrient 分组,并定义一个求最大值得函数,对 value 和 food 求最大值,对最大 food 读取前 50 行,读取 Amino Acids 行数据。
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
RSA密钥对生成器 生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
Mermaid 预览与可视化编辑 基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
随机西班牙地址生成器 随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online
Gemini 图片去水印 基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
curl 转代码 解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online