Python数据分析实战:Pandas数据清洗、处理与聚合核心技巧
1. 从“数据沼泽”到“数据绿洲”:为什么你离不开pandas
如果你刚接触Python数据分析,或者已经写了几百行代码,但每次处理Excel、CSV数据时,依然感觉像是在泥潭里挣扎——打开文件、复制粘贴、手动筛选、写一堆循环计算,最后还可能因为一个格式错误前功尽弃。那么,今天聊的这个工具,就是把你从“数据沼泽”里捞出来的那根绳子。
我说的就是pandas。它不是动物园里那只黑白相间的萌物,而是一个基于Python的、开源的、专门为数据分析而生的库。它的名字源于“Panel Data”(面板数据),但你现在可以简单理解为“Python Data Analysis”的缩写。在数据科学和商业分析的圈子里,pandas的地位,就像木匠手里的锤子,厨师手里的菜刀,是吃饭的家伙,是基础中的基础。
为什么它如此重要?因为数据从来不是以“干净、规整、随时可用”的姿态出现的。你从业务系统导出的Excel,可能夹杂着合并单元格、空行和莫名其妙的“N/A”文本;你从网页爬下来的数据,可能日期格式千奇百怪,数字里混着中文逗号。pandas的核心价值,就是提供了一套高效、统一、直观的接口,让你能用几行代码,完成过去在Excel里需要手动操作半小时,甚至写几十行复杂循环才能完成的数据清洗、转换和分析工作。
我见过太多新手,一上来就想搞机器学习、深度学习,结果卡在数据预处理的第一步,被各种格式问题折磨得怀疑人生。其实,数据分析80%的时间都花在数据准备上,而pandas,就是帮你把这80%的时间压缩到20%的利器。它让你能专注于分析逻辑和业务洞察,而不是和数据的“脏乱差”做无谓的搏斗。
接下来的内容,我会抛开那些厚厚的官方文档和教科书式的罗列,直接带你上手最常用、最高频的pandas代码片段。这些代码不是孤立的命令,而是我多年在实际项目中反复使用、验证过的“组合拳”。我会解释每一行代码背后的意图,告诉你什么场景下该用哪个方法,以及我最常踩的那些坑。我们的目标不是背API,而是让你真正理解如何用pandas的思维,像搭积木一样,高效地解决真实的数据问题。
2. 万丈高楼平地起:数据读写的“正确姿势”
数据分析的第一步,永远是把数据“弄进来”。pandas支持读取几乎任何你能想到的格式:CSV、Excel、JSON、SQL数据库、HTML表格,甚至剪贴板。但“能读”和“读得好”是两回事。一个错误的参数,可能让你后续的分析全盘皆错。
2.1 读取CSV与Excel:细节决定成败
CSV和Excel是最常见的数据源。用pd.read_csv()和pd.read_excel()看似简单,但魔鬼藏在参数里。
import pandas as pd # 基础读取 df_csv = pd.read_csv('data.csv') df_excel = pd.read_excel('data.xlsx', sheet_name='Sheet1')这行代码能跑通,但很脆弱。真实的数据文件往往带着各种“惊喜”。
场景一:编码问题——中文乱码的罪魁祸首如果你的CSV文件里有中文,打开后发现是“锟斤拷”之类的乱码,十有八九是编码问题。Windows系统下生成的CSV常用gbk或gb2312编码,而read_csv默认使用utf-8。
# 尝试指定编码 df = pd.read_csv('data.csv', encoding='gbk') # 如果还不行,试试常见的其他编码 # df = pd.read_csv('data.csv', encoding='gb2312') # df = pd.read_csv('data.csv', encoding='utf-8-sig')提示:一个快速判断编码的小技巧是用记事本打开文件,然后点击“文件”->“另存为”,在保存对话框底部可以看到当前文件的编码格式。
场景二:表头不在第一行有些导出的数据,前几行是说明信息,真正的表头在第4行。
df = pd.read_csv('data.csv', header=3) # 指定第4行(0-based索引)作为列名场景三:没有表头,需要自己指定数据文件就是纯数据,没有列名。
# 方法1:读取时不指定列名,pandas会自动分配0,1,2... df = pd.read_csv('data.csv', header=None) # 方法2:读取时直接指定列名(推荐,更清晰) df = pd.read_csv('data.csv', header=None, names=['日期', '销售额', '门店', '产品'])场景四:需要读取特定列文件很大,有几十列,但你只关心其中几列。
df = pd.read_csv('data.csv', usecols=['日期', '销售额', '产品'])这样做不仅能节省内存,还能加快读取速度,尤其是在处理大型文件时。
场景五:处理“脏”数据数据里可能有缺失值,用“-”、“NA”、“NULL”等表示,希望pandas能自动识别为NaN。
df = pd.read_csv('data.csv', na_values=['-', 'NA', 'NULL', ''])对于Excel文件,还有一个常见坑是“数据类型推断”。Excel里一个单元格看起来是数字,但可能存储为文本。pandas读取时,如果一列里混有数字和文本,整列可能会被识别为object类型,影响后续计算。
# 读取时指定某列为字符串类型 df = pd.read_excel('data.xlsx', dtype={'员工工号': str})2.2 数据预览与基本信息探查:你的第一份“体检报告”
数据读进来了,别急着分析。先给它做个体检,了解它的“身体状况”。
# 1. 查看前5行和后5行 print(df.head()) # 默认前5行 print(df.tail(3)) # 查看后3行 # 2. 查看数据形状(行数,列数) print(df.shape) # 输出 (100, 5) 表示100行5列 # 3. 查看列名、数据类型和非空值数量 print(df.info())df.info()是我最常用的初步诊断工具。它能一眼告诉你:
- 每列的名称。
- 每列的数据类型(
int64,float64,object等)。object类型通常是字符串或混合类型,需要警惕。 - 每列的非空值数量。如果
非空数远小于总行数,说明这列存在大量缺失值。 - 内存占用。对于大数据集,这个信息很重要。
一个真实的踩坑案例:我曾分析一个用户行为数据集,df.info()显示“用户ID”列是object类型。我没在意,直接开始分组统计。结果发现分组数量巨大,性能极差。最后才发现,这列数据里混入了少数几个像“123-abc”这样的字符串ID,导致pandas将整列判定为文本。用df[‘用户ID’].unique()[:20]快速查看一些唯一值后,才定位到问题。解决方法是用errors=’coerce’参数尝试转换,将无法转换的设为NaN。
# 4. 查看数值型列的快速统计摘要 print(df.describe())df.describe()默认只针对数值列(int和float),给出计数、均值、标准差、最小值、四分位数和最大值。它能帮你快速发现异常值,比如销售额有负数,或者年龄出现了999这样的离谱值。
# 5. 查看所有列(包括非数值列)的统计摘要 print(df.describe(include='all'))2.3 数据写出:保存你的劳动成果
处理完的数据,总要保存下来。最常用的还是CSV和Excel。
# 保存为CSV df.to_csv('cleaned_data.csv', index=False, encoding='utf-8-sig') # 保存为Excel df.to_excel('report.xlsx', index=False, sheet_name='月度报告')关键参数解析:
index=False:强烈建议加上。如果不加,pandas会把行索引(0,1,2…)也作为一列写入文件,这在绝大多数情况下都是多余的,还会让接手的同事困惑。encoding=’utf-8-sig’:保存CSV时使用带BOM的UTF-8编码,可以确保在Windows的Excel中打开时,中文字符正常显示,不会乱码。sheet_name:指定Excel工作表的名字。
3. 数据清洗与预处理:从“毛坯房”到“精装修”
原始数据就像毛坯房,直接没法住人。数据清洗就是装修,是最耗时但也最体现功力的环节。
3.1 处理缺失值:面对“空白”的哲学
数据缺失是常态。处理方式无非三种:删除、填充、保留。选择哪种,取决于业务逻辑和缺失比例。
(1)探查缺失情况
# 查看每列缺失值的数量 print(df.isnull().sum()) # 查看每列缺失值的比例(更直观) print(df.isnull().sum() / len(df) * 100)(2)删除缺失值最简单粗暴,也最可能丢失信息。适用于缺失比例极低,或该行/列确实不重要的情况。
# 删除任何包含缺失值的行 df_dropped_rows = df.dropna() # 删除任何包含缺失值的列 df_dropped_cols = df.dropna(axis=1) # 只删除在‘销售额’和‘成本’这两列上同时缺失的行 df_dropped_specific = df.dropna(subset=['销售额', '成本'])(3)填充缺失值更常用的方法。填充什么值,需要思考。
# 用固定值填充,例如用0填充 df_filled_0 = df.fillna(0) # 用前向填充(用上一个有效值填充) df_filled_ffill = df.fillna(method='ffill') # 用后向填充(用下一个有效值填充) df_filled_bfill = df.fillna(method='bfill') # 用该列的统计值填充,例如用均值填充‘年龄’ mean_age = df['年龄'].mean() df['年龄'].fillna(mean_age, inplace=True) # inplace=True表示直接修改原df # 分组填充:更高级的方法。例如,不同城市的平均工资不同,缺失的工资用该城市的平均工资填充 df['工资'] = df.groupby('城市')['工资'].transform(lambda x: x.fillna(x.mean()))注意:
inplace=True参数会直接修改原始的DataFrame,操作不可逆。对于重要的数据清洗步骤,我个人的习惯是尽量不使用inplace=True,而是将结果赋值给一个新变量(如df_cleaned = df.fillna(...))。这样保留了原始数据的副本,方便回溯和对比。只有在确定操作无误,且原始数据不再需要时,才考虑使用inplace=True。
3.2 处理重复值:消灭“双胞胎”
重复数据会扭曲分析结果,比如让销售总额虚高。
# 查看重复行(所有列值完全相同的行) print(df.duplicated().sum()) # 查看具体是哪些行重复了 duplicate_rows = df[df.duplicated(keep=False)] # keep=False标记所有重复项 print(duplicate_rows) # 基于特定列判断重复,例如‘订单ID’应该唯一 print(df.duplicated(subset=['订单ID']).sum()) # 删除重复行,默认保留第一次出现的行 df_deduped = df.drop_duplicates() # 删除重复行,保留最后一次出现的行 df_deduped_last = df.drop_duplicates(keep='last') # 基于‘订单ID’删除重复,保留第一个 df_deduped_id = df.drop_duplicates(subset=['订单ID'])3.3 数据类型转换:给数据“上户口”
错误的数据类型会导致计算错误或性能下降。最常见的是该是数字的列被识别为object(文本)。
# 查看各列数据类型 print(df.dtypes) # 转换单列数据类型 df['销售额'] = pd.to_numeric(df['销售额'], errors='coerce') # 将字符串转为数字,无效的转为NaN df['日期'] = pd.to_datetime(df['日期'], errors='coerce') # 转为日期时间类型 df['类别'] = df['类别'].astype('category') # 转为分类类型,节省内存,加速分组 # 转换多列数据类型 df = df.astype({'列A': 'int32', '列B': 'float64'}) # 批量转换:将所有‘object’类型的列尝试转为字符串(更节省内存的string类型,如果pandas版本支持) for col in df.select_dtypes(include=['object']).columns: df[col] = df[col].astype('string')关于errors=’coerce’:这是一个非常重要的安全阀。当转换过程遇到无法解析的值(如把“abc”转成数字)时,它会将其设置为NaN,而不是直接抛出错误导致程序中断。这让你有机会后续检查这些异常值。
3.4 字符串处理与列操作:数据整形术
数据经常需要拆分、合并、提取。
# 示例数据:df有一列‘姓名_部门’,格式如‘张三_技术部’ df['姓名_部门'].str.split('_', expand=True) # 拆分,expand=True返回DataFrame df[['姓名’, ‘部门’]] = df[‘姓名_部门’].str.split(‘_’, expand=True) # 直接拆分成新列 # 字符串替换与清洗 df[‘地址’] = df[‘地址’].str.replace(‘市’, ‘’).str.strip() # 去掉‘市’字并去除首尾空格 df[‘电话’] = df[‘电话’].str.extract(r’(\d{11})’) # 用正则表达式提取11位手机号 # 创建新列(派生列) df[‘销售额_万元’] = df[‘销售额’] / 10000 df[‘是否达标’] = df[‘销售额’] > 10000 # 生成布尔列 df[‘利润率’] = (df[‘利润’] / df[‘销售额’]).round(4) # 计算并保留4位小数 # 重命名列 df = df.rename(columns={‘old_name’: ‘new_name’, ‘销售额’: ‘sales’}) # 删除列 df = df.drop(columns=[‘无用列1’, ‘无用列2’])4. 数据筛选、排序与分组:聚焦你的分析目标
清洗完的数据,终于可以开始分析了。核心操作就是:找到你要的数据,按你的逻辑排序,然后分组聚合。
4.1 数据筛选:如何快速“捞”出你要的数据
筛选是数据分析中最频繁的操作。pandas提供了极其灵活和强大的布尔索引。
(1)基于条件的行筛选
# 单条件筛选:销售额大于10000的记录 high_sales = df[df[‘销售额’] > 10000] # 多条件“与”(and)筛选:销售额大于10000且来自‘北京’的记录 condition = (df[‘销售额’] > 10000) & (df[‘城市’] == ‘北京’) high_sales_bj = df[condition] # 多条件“或”(or)筛选:销售额大于10000或来自‘上海’的记录 condition = (df[‘销售额’] > 10000) | (df[‘城市’] == ‘上海’) target_sales = df[condition] # 条件“非”(not)筛选:城市不是‘北京’和‘上海’的记录 condition = ~df[‘城市’].isin([‘北京’, ‘上海’]) other_cities = df[condition] # 字符串模糊筛选:产品名称包含‘手机’的记录 condition = df[‘产品名称’].str.contains(‘手机’, na=False) # na=False处理NaN值 phone_products = df[condition](2)基于列名的列筛选
# 选择特定列 subset = df[[‘日期’, ‘销售额’, ‘产品’]] # 选择列名符合某种模式的列(需配合列表推导式) cols_start_with_s = [col for col in df.columns if col.startswith(‘s’)] df_s = df[cols_start_with_s](3).loc和.iloc:精准定位的“手术刀”.loc基于标签(label,即索引名和列名)进行筛选,.iloc基于整数位置(integer location)进行筛选。
# .loc 用法:先行后列,支持切片和布尔索引 df.loc[0, ‘销售额’] # 获取索引为0的行,列名为‘销售额’的单个值 df.loc[0:5, [‘日期’, ‘销售额’]] # 获取索引0到5的行,以及‘日期’和‘销售额’两列(注意:切片末端包含) df.loc[df[‘城市’] == ‘北京’, ‘销售额’] # 获取所有城市为北京的行的‘销售额’列 # .iloc 用法:基于整数位置 df.iloc[0, 1] # 获取第0行,第1列的值(从0开始计数) df.iloc[0:5, 0:2] # 获取前5行,前2列(切片末端不包含,与Python列表一致)4.2 数据排序:让数据“井然有序”
排序能帮你快速找到头部和尾部数据。
# 按单列排序,默认升序 df_sorted = df.sort_values(‘销售额’) # 按单列降序排序 df_sorted_desc = df.sort_values(‘销售额’, ascending=False) # 按多列排序:先按‘城市’升序,城市相同的再按‘销售额’降序 df_sorted_multi = df.sort_values([‘城市’, ‘销售额’], ascending=[True, False]) # 按索引排序 df_sorted_index = df.sort_index()4.3 数据分组与聚合:洞察的“显微镜”
分组聚合是数据分析的灵魂。它回答的是“每个X的Y是多少”这类问题,比如“每个城市的平均销售额是多少?”、“每个月的销售总额是多少?”。
(1)基础分组聚合
# 按‘城市’分组,计算‘销售额’的平均值 city_sales_mean = df.groupby(‘城市’)[‘销售额’].mean() # 按‘城市’和‘产品’分组,计算‘销售额’的总和与‘利润’的平均值 grouped = df.groupby([‘城市’, ‘产品’]).agg({‘销售额’: ‘sum’, ‘利润’: ‘mean’}) print(grouped)groupby之后接agg(aggregate的缩写)是最常用的模式。agg里可以传入一个字典,指定对哪些列进行哪些聚合运算。常见的聚合函数有:sum,mean,median,min,max,count,std(标准差),var(方差)等。
(2)重置索引:让分组结果更友好上面grouped的结果是一个多层索引的Series或DataFrame。有时我们希望把分组键(‘城市’,‘产品’)变回普通的列。
grouped_reset = grouped.reset_index()(3)分组后应用复杂函数agg处理简单聚合,apply则可以应用任意自定义函数。
# 计算每个城市销售额的变异系数(标准差/均值) def coefficient_of_variation(x): return x.std() / x.mean() cv_by_city = df.groupby(‘城市’)[‘销售额’].apply(coefficient_of_variation)(4)分组筛选:groupby+filter
# 筛选出至少有过3条销售记录的城市 df_filtered = df.groupby(‘城市’).filter(lambda x: len(x) >= 3)一个实战心得:分组聚合的结果,经常需要进一步处理或可视化。我习惯在分组后立即reset_index(),这样得到一个“干净”的DataFrame,后续用matplotlib或seaborn画图时会非常方便,不需要再处理多层索引。
5. 数据合并与连接:像拼图一样组合数据
现实中的数据往往分散在多个表里。pandas提供了类似SQL的join操作来合并它们。
5.1 纵向合并:pd.concat
用于将结构相同(列名相同)的多个DataFrame上下堆叠在一起。
# 假设df1, df2, df3结构相同 df_combined = pd.concat([df1, df2, df3], ignore_index=True)ignore_index=True会重置合并后的索引,否则会保留原来的索引,可能导致重复。
5.2 横向连接:pd.merge
用于基于一个或多个键(key)将不同的DataFrame连接起来,这是最像SQLJOIN的操作。
# 假设df_orders有‘订单ID’,‘客户ID’,‘金额’ # df_customers有‘客户ID’,‘客户名’,‘城市’ # 内连接(inner join):只保留两个表都有的‘客户ID’ df_inner = pd.merge(df_orders, df_customers, on=‘客户ID’, how=‘inner’) # 左连接(left join):以左表df_orders为准,保留所有订单,没有客户信息的填NaN df_left = pd.merge(df_orders, df_customers, on=‘客户ID’, how=‘left’) # 右连接(right join):以右表df_customers为准 df_right = pd.merge(df_orders, df_customers, on=‘客户ID’, how=‘right’) # 外连接(outer join):保留所有记录,没有对应关系的填NaN df_outer = pd.merge(df_orders, df_customers, on=‘客户ID’, how=‘outer’) # 基于多个键连接 df_complex = pd.merge(df1, df2, on=[‘年份’, ‘月份’], how=‘inner’) # 左右表连接键列名不同时 df_diff_key = pd.merge(df_orders, df_customers, left_on=‘order_cust_id’, right_on=‘cust_id’, how=‘inner’)连接操作的核心参数:
on:用于连接的列名。必须在两个DataFrame中都存在。how:连接方式。inner,left,right,outer。最常用的是left,因为它能保证左表(通常是你的主事实表,如订单表)的记录不丢失。suffixes:当两个表有同名列但不是连接键时,用于区分这些列的后缀。例如suffixes=(‘_订单’, ‘_客户’)。
5.3 一个常见的合并陷阱与排查
合并后数据变多或变少了?首先检查重复键和连接类型。
# 检查左表的连接键是否有重复 print(df_orders[‘客户ID’].duplicated().sum()) # 检查右表的连接键是否有重复 print(df_customers[‘客户ID’].duplicated().sum())如果连接键有重复,进行many-to-many连接,会导致数据行数爆炸式增长(笛卡尔积)。这时你需要先对数据进行去重或聚合,确保连接键唯一,或者明确这就是你想要的many-to-many关系。
6. 时间序列数据处理:与时间做朋友
很多业务数据都与时间相关。pandas对时间序列的支持是其一大杀手锏。
6.1 时间类型转换与属性提取
# 确保‘日期’列是datetime类型 df[‘日期’] = pd.to_datetime(df[‘日期’]) # 提取时间分量 df[‘年份’] = df[‘日期’].dt.year df[‘月份’] = df[‘日期’].dt.month df[‘季度’] = df[‘日期’].dt.quarter df[‘星期几’] = df[‘日期’].dt.dayofweek # 周一=0, 周日=6 df[‘是否周末’] = df[‘日期’].dt.dayofweek >= 5 df[‘月初’] = df[‘日期’].dt.to_period(‘M’).dt.to_timestamp() # 转换为当月第一天6.2 重采样与滚动窗口计算
这是时间序列分析的核心,用于将高频数据聚合为低频(如日数据变月数据),或计算移动平均等指标。
# 假设df已按‘日期’排序,且‘日期’是索引(如果不是,先用df.set_index(‘日期’, inplace=True)设置) df = df.set_index(‘日期’) # 按周重采样,计算每周销售额总和 weekly_sales = df[‘销售额’].resample(‘W’).sum() # ‘W’表示周, ‘M’表示月, ‘Q’表示季, ‘Y’表示年 # 按月重采样,计算每月销售额均值和订单数 monthly_stats = df.resample(‘M’).agg({‘销售额’: ‘mean’, ‘订单ID’: ‘count’}) # 计算7天滚动平均销售额,用于平滑曲线,观察趋势 df[‘销售额_7d_avg’] = df[‘销售额’].rolling(window=7).mean() # 计算过去30天的累计销售额 df[‘销售额_30d_sum’] = df[‘销售额’].rolling(window=30).sum()关于rolling的min_periods参数:默认情况下,窗口需要被完全填满(比如7天窗口需要前6个数据点)才开始计算。这会导致结果的前几个值为NaN。如果你希望只要有数据就开始算,可以设置min_periods=1。
df[‘销售额_7d_avg’] = df[‘销售额’].rolling(window=7, min_periods=1).mean()7. 高效迭代与性能优化:当数据量变大时
虽然pandas的向量化操作已经非常高效,应尽量避免显式循环(如for row in df.iterrows()),但在某些复杂逻辑下,我们仍需要考虑迭代。这里有一些更优的选择。
7.1 避免iterrows(), 使用itertuples()
iterrows()返回的是(index, Series)对,生成Series对象有开销,很慢。itertuples()返回命名元组,速度快得多。
# 慢:不推荐用于大数据集 for index, row in df.iterrows(): # 用row[‘列名’]访问数据 # 快:推荐 for row in df.itertuples(): # 用row.列名 访问数据, 例如 row.销售额 # 索引可以通过row.Index访问7.2 终极武器:apply与向量化
apply方法可以将一个函数应用到DataFrame的某一轴(行或列)上,它底层是Cython优化过的,通常比Python层级的循环快。
# 对每一行应用一个函数,创建新列 def calculate_profit_margin(row): if row[‘销售额’] > 0: return row[‘利润’] / row[‘销售额’] else: return 0 df[‘利润率’] = df.apply(calculate_profit_margin, axis=1) # axis=1 表示按行 # 对某一列应用函数 df[‘城市_缩写’] = df[‘城市’].apply(lambda x: x[:2]) # 取城市名前两个字但是,apply也不是万能的。如果可能,尽量使用pandas内置的向量化函数(如.str方法, 算术运算),它们的速度是最快的。
# 向量化操作:速度最快,首选 df[‘利润率’] = df[‘利润’] / df[‘销售额’] df[‘利润率’] = df[‘利润率’].where(df[‘销售额’] > 0, 0) # 处理除零情况 # 使用np.where进行条件向量化赋值 import numpy as np df[‘等级’] = np.where(df[‘销售额’] > 10000, ‘高’, ‘低’)7.3 处理大数据集的技巧:分块与数据类型优化
当数据文件大到内存无法一次性加载时:
# 分块读取CSV chunk_size = 100000 # 每次读10万行 chunks = [] for chunk in pd.read_csv(‘huge_data.csv’, chunksize=chunk_size): # 对每个chunk进行必要的处理 processed_chunk = do_some_processing(chunk) chunks.append(processed_chunk) # 最后将所有处理好的块合并 df_processed = pd.concat(chunks, ignore_index=True)优化数据类型以节省内存:
# 查看内存使用 print(df.info(memory_usage=‘deep’)) # 将数值列从int64/float64向下转换 df[‘小整数列’] = df[‘小整数列’].astype(‘int32’) df[‘小数列’] = df[‘小数列’].astype(‘float32’) # 将文本列从object转为category(如果唯一值较少) if df[‘城市’].nunique() / len(df) < 0.5: # 唯一值比例小于50% df[‘城市’] = df[‘城市’].astype(‘category’)8. 实战案例串联:一个简单的销售数据分析流程
让我们把上面的代码片段串起来,模拟一个从数据加载到产出洞察的简单流程。
假设我们有一个sales_data.csv文件,包含date(日期),city(城市),product(产品),sales(销售额),profit(利润)字段。
import pandas as pd import numpy as np # 1. 加载与探查 print(“步骤1: 加载数据...”) df = pd.read_csv(‘sales_data.csv’, encoding=‘utf-8-sig’) print(“数据形状:”, df.shape) print(“\n数据预览:”) print(df.head()) print(“\n数据信息:”) print(df.info()) print(“\n描述性统计:”) print(df.describe()) # 2. 数据清洗 print(“\n步骤2: 数据清洗...”) # 检查缺失 print(“缺失值统计:”) print(df.isnull().sum()) # 假设我们决定用0填充利润的缺失值,删除销售额缺失的行 df[‘profit’].fillna(0, inplace=True) df.dropna(subset=[‘sales’], inplace=True) # 处理日期 df[‘date’] = pd.to_datetime(df[‘date’]) df[‘year’] = df[‘date’].dt.year df[‘month’] = df[‘date’].dt.month df[‘quarter’] = df[‘date’].dt.quarter # 3. 核心分析 print(“\n步骤3: 核心分析...”) # 按城市和产品分析销售额和利润 city_product_analysis = df.groupby([‘city’, ‘product’]).agg({ ‘sales’: [‘sum’, ‘mean’, ‘count’], ‘profit’: ‘sum’ }).round(2) city_product_analysis.columns = [‘销售额_总和’, ‘销售额_均值’, ‘订单数’, ‘利润_总和’] # 扁平化列名 print(“按城市和产品分析:”) print(city_product_analysis.head(10)) # 计算整体利润率 df[‘profit_margin’] = (df[‘profit’] / df[‘sales’]).replace([np.inf, -np.inf], np.nan).fillna(0) overall_margin = df[‘profit_margin’].mean() print(f”\n整体平均利润率: {overall_margin:.2%}“) # 月度趋势分析 monthly_trend = df.set_index(‘date’).resample(‘M’)[‘sales’].sum() print(“\n月度销售额趋势:”) print(monthly_trend.head()) # 4. 输出结果 print(“\n步骤4: 保存结果...”) # 保存清洗后的数据 df.to_csv(‘cleaned_sales_data.csv’, index=False, encoding=‘utf-8-sig’) # 保存分析报告 city_product_analysis.to_excel(‘sales_analysis_report.xlsx’, sheet_name=‘城市产品分析’) print(“分析完成!结果已保存。”)这个流程覆盖了读取、清洗、转换、分组聚合、时间序列分析和结果输出的完整链条。你可以根据自己数据的实际情况,调整其中的每一步。记住,pandas是一个工具库,真正的魔法在于你如何组合这些基础操作来解决具体的业务问题。多练,多踩坑,你很快就能得心应手。
