Python数据分析实战:从数据清洗到可视化展示
1. 为什么说数据清洗是数据分析的“脏活累活”?
我刚开始学数据分析那会儿,总觉得写代码画图最酷,恨不得拿到数据就直接上各种炫酷的图表。结果呢?现实给了我当头一棒。数据里充满了各种“惊喜”:日期格式五花八门,有的写“2023-01-01”,有的写“2023/1/1”,还有的干脆写“23年1月1号”;金额字段里混着中文“元”和英文符号;甚至有些行干脆就是空的。直接拿这样的数据去做计算、画图,要么报错,要么得出完全错误的结论。后来我才明白,数据分析师80%的时间其实都在和数据“搏斗”,也就是做数据清洗。这活儿确实有点枯燥,像给数据“洗澡”、“理发”、“换衣服”,但它是整个流程的基石,地基打不牢,后面盖多高的楼都容易塌。
数据清洗听起来高大上,其实核心目标就一个:把原始数据变成一份干净、一致、可用的数据。想象一下,你是个厨师,要做一道菜。数据清洗就是处理食材的过程:你得把烂叶子摘掉(删除无效数据)、把土豆上的泥洗干净(修正错误值)、把大小不一的胡萝卜切成均匀的丁(统一格式)。不经过这些步骤,你没法开始炒菜。在Python里,我们主要用pandas这个库来完成这些工作。它就像数据分析的“瑞士军刀”,功能强大又顺手。下面我就用一个模拟的销售数据例子,带你走一遍最常见的清洗步骤。
假设我们有一份从某个老旧系统导出的销售记录CSV文件,名叫sales_dirty.csv。我们先用pandas把它读进来看看。
import pandas as pd
# 读取原始数据
df = pd.read_csv('sales_dirty.csv')
print("数据预览:")
print(df.head())
print("\n数据信息:")
print(df.info())
运行后你可能会看到各种问题:Date列是object类型(文本),而不是日期类型;Revenue列里有些值是字符串"N/A"或"未知";CustomerID列有重复;Product列的产品名大小写混乱,比如“Laptop”和“laptop”被当成两种产品。针对这些问题,我们一步步来清洗。
第一步,处理缺失值和无效值。 对于金额(Revenue)列,"N/A"或"未知"显然不能参与计算。我们可以用fillna()填充,或者用dropna()删除。但更常见的做法是先替换这些无效字符串为真正的空值NaN,再处理。
# 将‘Revenue’列中的无效字符串替换为NaN
df['Revenue'] = pd.to_numeric(df['Revenue'], errors='coerce')
# 用该列的平均值填充NaN
df['Revenue'].fillna(df['Revenue'].mean(), inplace=True)
第二步,统一格式和类型。 把Date列转换成标准的日期时间格式,方便后续按日、月聚合。
df['Date'] = pd.to_datetime(df['Date'], errors='coerce')
第三步,处理重复值。 完全重复的行没有意义,直接删除。
df.drop_duplicates(inplace=True)
第四步,规范文本数据。 把Product列的产品名全部变成首字母大写,消除因大小写不一致导致的分类错误。
df['Product'] = df['Product'].str.title()
你看,就这么几步,一份乱七八糟的数据就变得规整多了。这还只是基础操作,实际工作中你可能会遇到更复杂的情况,比如需要根据业务规则合并多列、拆分字段、或者处理异常值(比如金额为负数的订单)。但万变不离其宗,核心思路就是识别问题 -> 选择策略 -> 应用转换。把数据清洗好了,后面的分析和可视化就是水到渠成的事,否则你会一直被各种奇怪的错误困扰。
2. 如何用Pandas把数据“玩出花”:聚合、透视与合并
数据清洗干净后,我们手里拿到的是一份“整洁”的表格。但这份表格通常还是最原始的“交易明细”级别,比如每一行就是一条销售记录。老板或者业务部门往往不关心某一天具体卖了哪一台电脑,他们更想知道:这个月每天的销售额趋势是怎样的?哪个产品品类卖得最好?不同地区的销售贡献占比如何? 要回答这些问题,我们就需要对数据进行“再加工”,也就是数据聚合与转换。这就像是把一堆零散的乐高积木,按照图纸拼成城堡、汽车等有意义的模型。
pandas在这方面提供了极其强大的工具,我把它叫做“数据变形金刚”。最常用的三个功能是:分组聚合(GroupBy)、数据透视表(Pivot Table)和多表合并(Merge/Concat)。我们继续用销售数据的例子。假设现在我们有了一份清洗后的数据框df,包含Date(日期)、Product(产品)、Region(地区)、Revenue(销售额)这几列。
第一个需求:计算每个产品的总销售额。 这就是典型的分组聚合操作。
# 按产品分组,并计算每组的销售额总和
product_sales = df.groupby('Product')['Revenue'].sum().reset_index()
print(product_sales)
groupby('Product')意思是“按产品分组”,把相同产品的所有行放到一组。['Revenue'].sum()表示对每个组内的Revenue列进行求和。.reset_index()是为了把分组依据(Product)从索引变回普通列,方便后续操作。结果你会得到一个两列的新表格,一列是产品名,一列是该产品的总销售额。
第二个需求:更复杂一点,我想看每个地区、每个产品的销售额。 这可以用数据透视表pivot_table,它比groupby更直观,结果更像Excel表格。
# 创建数据透视表,行是地区,列是产品,值是销售额总和
pivot_table = pd.pivot_table(df, values='Revenue', index='Region', columns='Product', aggfunc='sum', fill_value=0)
print(pivot_table)
这个命令生成一个矩阵:行是各个地区,列是各个产品,交叉的单元格就是该地区该产品的销售额总和。fill_value=0表示如果某个组合没有数据(比如A地区没卖过B产品),就填0。透视表特别适合做交叉分析和制作热力图的数据源。
第三个需求:我们有两份数据,一份是1月的销售记录df_jan,一份是2月的df_feb,如何合并起来看整体情况? 如果两份数据的列结构完全一样,只是时间不同,可以用pd.concat简单拼接。
df_all = pd.concat([df_jan, df_feb], ignore_index=True)
如果两份数据有关联键,比如一份是订单主表(订单ID,客户ID,日期),一份是订单明细表(订单ID,产品,金额),我们需要根据订单ID把它们关联起来,就要用pd.merge。
# 类似SQL的JOIN操作,根据‘order_id’连接两个表
order_details = pd.merge(order_main, order_items, on='order_id', how='inner')
how='inner'表示取两个表都有的订单ID(内连接),还有left、right、outer等连接方式,对应SQL里的各种JOIN。掌握这些操作后,你就能灵活地把数据“揉捏”成任何业务需要的形状,为下一步的可视化准备好“食材”。
3. 从枯燥数字到生动图表:Matplotlib与Seaborn快速上手
数据整理好了,一堆数字表格摆在眼前,别说老板了,自己看着都头晕。这时候,数据可视化就派上用场了。它的作用是把抽象的数字变成直观的图形,让人一眼就能看出趋势、发现异常、理解关系。在Python里,最经典的可视化库是Matplotlib,它就像绘图界的“底层工兵”,功能全面,几乎什么图都能画,但需要设置的参数比较多。而在它基础上封装的Seaborn库,则像是“美学设计师”,默认样式就很好看,绘制统计图表尤其方便,几行代码就能出很专业的图。
我们先从Matplotlib开始。假设我们已经有了一个按日期聚合的每日销售额数据daily_sales(一个pandas的Series,索引是日期,值是销售额)。画一个最简单的折线图来看趋势。
import matplotlib.pyplot as plt
# 设置中文字体,防止标签显示为方框(根据系统调整字体路径)
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
# 创建画布和坐标系
fig, ax = plt.subplots(figsize=(12, 6))
# 绘制折线图
ax.plot(daily_sales.index, daily_sales.values, marker='o', linewidth=2, color='steelblue')
# 设置标题和标签
ax.set_title('2023年1月每日销售额趋势', fontsize=16, fontweight='bold')
ax.set_xlabel('日期', fontsize=12)
ax.set_ylabel('销售额(元)', fontsize=12)
# 旋转x轴日期标签,避免重叠
plt.xticks(rotation=45)
# 自动调整布局,显示网格线
plt.tight_layout()
ax.grid(True, linestyle='--', alpha=0.6)
# 显示图表
plt.show()
这段代码做了几件事:创建画布、画折线、加标题和坐标轴标签、调整样式、最后显示。Matplotlib的灵活性很高,你可以精细控制线条颜色、粗细、标记点样式、图例位置等几乎所有元素。但正因为太灵活,想要调出一个既美观又信息量足的图,往往需要写不少代码。
这时候Seaborn的优势就体现出来了。比如,我想看不同产品在不同地区的销售额分布,用箱线图(Boxplot)最合适。用Matplotlib画箱线图比较麻烦,而用Seaborn几乎是一行代码的事。
import seaborn as sns
# 设置Seaborn主题风格
sns.set_style("whitegrid")
# 绘制箱线图:x轴是产品,y轴是销售额,按地区分组着色
plt.figure(figsize=(10, 6))
sns.boxplot(data=df, x='Product', y='Revenue', hue='Region', palette='Set2')
plt.title('不同产品在各地区的销售额分布', fontsize=15)
plt.xlabel('产品')
plt.ylabel('销售额')
plt.xticks(rotation=15) # 如果产品名较长,可以旋转一下
plt.legend(title='地区')
plt.tight_layout()
plt.show()
Seaborn的boxplot函数自动帮你计算了中位数、四分位数、异常值,并按hue参数指定的Region列进行了分组着色,图表瞬间变得既专业又美观。Seaborn还擅长画分布图(distplot)、热力图(heatmap)、成对关系图(pairplot)等统计图表。我的经验是:快速探索数据、绘制统计图表时,优先用Seaborn;当需要高度定制化、或者画一些特殊图表(如甘特图、雷达图)时,再用Matplotlib打底。 两者结合使用,效率最高。
4. 打造交互式商业报表:PyEcharts实战进阶
静态图表虽然好,但在做汇报或者构建数据看板时,我们往往希望图表是交互式的:鼠标悬停可以显示具体数值,点击图例可以隐藏/显示某个系列,用滑块筛选时间范围……这种动态图表能让数据故事讲得更生动。在Python生态里,PyEcharts是制作交互式图表的利器。它基于百度开源的ECharts库,图表类型丰富,效果炫酷,而且生成的HTML文件可以独立在浏览器中打开,非常适合做数据演示。
我们延续最开始的案例,目标是生成一个展示每日销售额的交互式柱状图。原始文章里已经给出了核心代码,我这里再补充一些细节和美化技巧。首先,你需要安装pyecharts库:pip install pyecharts。
核心步骤分为三步:准备数据、配置图表、渲染输出。数据部分我们已经通过pandas处理好了,存在一个字典data_dict里,键是日期,值是销售额。
from pyecharts.charts import Bar
from pyecharts import options as opts
from pyecharts.globals import ThemeType
# 1. 准备数据
dates = list(data_dict.keys()) # X轴数据:日期列表
sales = list(data_dict.values()) # Y轴数据:销售额列表
# 2. 创建柱状图对象,并设置主题(这里用亮色系)
bar = Bar(init_opts=opts.InitOpts(theme=ThemeType.LIGHT, width="1000px", height="600px"))
# 3. 添加数据
bar.add_xaxis(dates)
bar.add_yaxis("销售额", sales,
label_opts=opts.LabelOpts(is_show=False), # 不显示柱子上方的数字标签,避免拥挤
itemstyle_opts=opts.ItemStyleOpts(color="#5793f3") # 设置柱子颜色
)
# 4. 设置全局配置项(这是让图表变好看的关键!)
bar.set_global_opts(
title_opts=opts.TitleOpts(title="2023年1-2月每日销售额", subtitle="数据来源:模拟销售数据",
title_textstyle_opts=opts.TextStyleOpts(font_size=18)),
tooltip_opts=opts.TooltipOpts(trigger="axis", axis_pointer_type="shadow"),
# 工具提示:鼠标悬停时显示该日期的详细信息
xaxis_opts=opts.AxisOpts(name="日期", axislabel_opts=opts.LabelOpts(rotate=45)),
# X轴配置:名称,标签旋转45度防重叠
yaxis_opts=opts.AxisOpts(name="销售额(元)",
axislabel_opts=opts.LabelOpts(formatter="{value} 元")),
# Y轴配置:名称,标签格式化为“xxx元”
datazoom_opts=[opts.DataZoomOpts()] # 添加数据区域缩放组件,可以用鼠标拖拽查看局部
)
# 5. 渲染图表到HTML文件
bar.render("daily_sales_bar_chart.html")
运行这段代码后,会在当前目录生成一个daily_sales_bar_chart.html文件。用浏览器打开它,你就能看到一个可以交互的柱状图了:鼠标移到柱子上会显示具体日期和销售额;下方有缩放滑块可以聚焦查看某段时间;右上角有工具框可以保存为图片。这比静态图片强太多了。
PyEcharts的强大之处在于其丰富的图表类型和灵活的配置。除了柱状图,你还可以轻松绘制折线图(Line)、饼图(Pie)、散点图(Scatter)、地图(Map)等等。而且,它支持将多个图表组合到一个页面,形成仪表盘。比如,你可以把销售额趋势折线图、产品占比饼图、地区分布地图放在一个HTML页面里,形成一个简单的销售数据看板。对于初学者来说,先从模仿官方示例(官网Gallery有很多)开始,然后根据自己的数据修改,是最快的学习路径。
5. 构建完整数据分析流水线:一个电商销售分析实战
前面我们把数据分析的各个环节拆开讲了一遍,现在我们来串成一个完整的、有实际意义的项目。假设你在一家电商公司,老板给你两个月的销售数据(一个TXT文件,一个JSON文件,就像原始文章里那样),让你分析一下销售情况,并做一份可视化报告。这个实战项目会用到我们讲过的所有技能点。
第一步:明确分析目标。 我们不能为了分析而分析。和业务方沟通后,确定了几个关键问题:1. 整体销售额随时间的变化趋势是怎样的?2. 哪些省份是销售主力?3. 有没有明显的销售高峰或低谷?原因可能是什么?
第二步:数据读取与清洗。 原始文章已经给出了很好的面向对象设计,定义了Record数据类和FileReader抽象类。在实际工作中,我们可能更倾向于用pandas直接读取,因为更快捷。但理解面向对象的设计思想对于构建大型数据管道很有帮助。这里我们采用一种折中方案,先用自定义类读取并转换成Record对象列表,再转为pandas DataFrame,这样既能练习面向对象,又能享受pandas的便利。
import pandas as pd
from data_define import Record
from file_define import TextFileReader, JsonFileReader
# 1. 使用自定义的Reader类读取数据
text_reader = TextFileReader("2011年1月销售数据.txt")
json_reader = JsonFileReader("2011年2月销售数据JSON.txt")
jan_records = text_reader.read_data()
feb_records = json_reader.read_data()
# 2. 将Record对象列表转换为pandas DataFrame
def records_to_df(records: list[Record]) -> pd.DataFrame:
data = []
for r in records:
data.append([r.date, r.order_id, r.money, r.province])
return pd.DataFrame(data, columns=['date', 'order_id', 'money', 'province'])
df_jan = records_to_df(jan_records)
df_feb = records_to_df(feb_records)
# 3. 合并数据并清洗
df_all = pd.concat([df_jan, df_feb], ignore_index=True)
# 确保日期列是datetime类型
df_all['date'] = pd.to_datetime(df_all['date'])
# 检查是否有空值或异常值
print(df_all.info())
print(df_all.describe())
第三步:数据聚合与分析。 针对我们的业务问题,进行数据聚合。
# 问题1:每日销售额趋势
daily_sales = df_all.groupby(df_all['date'].dt.date)['money'].sum()
# 这里用.dt.date只取日期部分,忽略时间(如果有时分秒的话)
# 问题2:各省份销售额排名
province_sales = df_all.groupby('province')['money'].sum().sort_values(ascending=False)
# 问题3:找出销售额最高的前N天和最低的后N天,供后续分析原因
top_n_days = daily_sales.nlargest(5)
bottom_n_days = daily_sales.nsmallest(5)
第四步:多维度可视化展示。 用PyEcharts制作一个包含多个图表的仪表盘。
from pyecharts.charts import Bar, Line, Pie, Page
from pyecharts import options as opts
# 1. 每日销售额趋势(折线图)
line = (
Line()
.add_xaxis(daily_sales.index.astype(str).tolist())
.add_yaxis("销售额", daily_sales.values.round(2).tolist(),
is_smooth=True, label_opts=opts.LabelOpts(is_show=False))
.set_global_opts(title_opts=opts.TitleOpts(title="每日销售额趋势"),
tooltip_opts=opts.TooltipOpts(trigger="axis"),
datazoom_opts=[opts.DataZoomOpts()])
)
# 2. 省份销售额排名(横向柱状图)
bar = (
Bar()
.add_xaxis(province_sales.index.tolist())
.add_yaxis("销售额", province_sales.values.round(2).tolist())
.reversal_axis() # 横向显示
.set_series_opts(label_opts=opts.LabelOpts(position="right"))
.set_global_opts(title_opts=opts.TitleOpts(title="各省份销售额排名"),
xaxis_opts=opts.AxisOpts(name="销售额"),
yaxis_opts=opts.AxisOpts(name="省份"))
)
# 3. 销售额Top5省份占比(饼图)
pie = (
Pie()
.add("", [list(z) for z in zip(province_sales.head(5).index.tolist(),
province_sales.head(5).values.round(2).tolist())])
.set_global_opts(title_opts=opts.TitleOpts(title="销售额Top5省份占比"),
legend_opts=opts.LegendOpts(type_="scroll", orient="vertical", pos_left="left"))
.set_series_opts(label_opts=opts.LabelOpts(formatter="{b}: {c} ({d}%)"))
)
# 4. 将三个图表组合到一个页面
page = Page(layout=Page.SimplePageLayout)
page.add(line, bar, pie)
page.render("sales_analysis_dashboard.html")
运行后生成的dashboard.html,就是一个包含趋势图、排名图、占比图的交互式报告。你可以把这个文件直接发给老板,他点开就能看到清晰、直观、可交互的分析结果。这个完整的流程,从脏数据到洞察,就是数据分析师日常工作的一个缩影。多练习几个这样的完整案例,你就能建立起自己的数据分析“肌肉记忆”,面对新的数据集时,也能有条不紊地拆解、清洗、分析和呈现。
更多推荐


所有评论(0)