Pandas实战技巧——头歌平台数据分析进阶指南
1. 从零开始:理解Pandas分组聚合的核心逻辑
如果你用过Excel的数据透视表,那你对分组聚合的概念应该不陌生。简单来说,就是把一堆杂乱的数据,按照某个“标签”分门别类,然后对每一类数据进行“总结”,比如算个总和、求个平均。在Pandas里,这个“分门别类”的动作就是groupby,而“总结”的动作就是agg(aggregate的缩写)。听起来很简单对吧?但很多新手,包括当年的我,一开始都会卡在一个地方:分组之后,到底该怎么灵活地计算我想要的指标?
头歌平台上的第一关练习题,就给了我们一个非常典型的场景。它有一份关于各大洲酒类消费的数据(drinks.csv),要求我们按“洲”(continent)分组,然后对“葡萄酒消费量”(wine_servings)计算一个自定义的指标(最大值减最小值),同时对“啤酒消费量”(beer_servings)计算总和。原始答案的代码很精炼,但我想带你一步步拆开看,理解背后的“为什么”和“还可以怎么做”。
首先,我们得把数据读进来。pd.read_csv是最常用的方法,这里路径是"step1/drinks.csv"。读完之后,通常我们会习惯性地用pd.DataFrame再包装一下,虽然对于CSV文件读进来的数据它已经是DataFrame了,但这样写更清晰。关键步骤来了:df.groupby("continent")。这一步执行后,数据并没有立刻变成一个新的表格,而是产生了一个“分组对象”。你可以把它想象成Pandas把数据按照“Africa”、“Asia”等分成了好几个小抽屉,但抽屉还没打开。
接下来,agg函数就是打开抽屉,对里面的东西进行加工。这里最巧妙的地方在于mapping字典的用法。我见过很多新手会分开写两个agg,或者写很长的匿名函数,其实用字典映射是最清晰、最Pythonic的方式。字典的键(key)是你要操作的列名,值(value)是你想对这个列应用的函数。这个函数可以是Pandas内置的(像np.sum),也可以是完全自定义的,就像这里的sub函数。
自定义函数sub的定义是def sub(df): return df.max() - df.min()。这里有个细节:agg在应用这个函数时,传入的df参数是什么?它传入的是每个分组里,wine_servings这一列的数据,是一个Series。所以df.max()和df.min()就是在这个分组内求该列的最大值和最小值。这种写法非常灵活,你完全可以把sub函数改成计算变异系数、去极值平均等等任何你想要的复杂逻辑。
运行print(df.groupby("continent").agg(mapping))后,你会得到一个以continent为索引的新DataFrame,列是wine_servings和beer_servings,但值已经分别是我们自定义的极差和总和了。这个结果比原始的逐行数据清晰太多,一眼就能看出哪个洲的葡萄酒消费波动大,哪个洲的啤酒消费总量高。
在实际工作中,agg的功能远不止于此。你可以同时对多列应用多个聚合函数,比如df.groupby("continent").agg({"wine_servings": ["mean", "std"], "beer_servings": "sum"}),这样会生成一个多层级的列索引,信息量爆炸。掌握好groupby和agg的组合,是你从“会用Pandas”到“善用Pandas”的第一个关键台阶。
2. 透视表与交叉表:多维数据洞察的利器
分组聚合很好,但它通常是一维的(按一个维度分组)。当我们想同时看两个甚至更多维度对数据的影响时,比如“每天不同时间段的小费总额”,就需要更强大的工具——透视表(pivot_table)和交叉表(crosstab)。头歌平台的第二关,就用一个餐厅小费数据集(tip.csv)完美诠释了这两者的应用。
先说说透视表。在Excel里,你肯定拖拽过行、列、值来生成透视表。Pandas的pivot_table方法就是干这个的,而且功能更强大。原始答案中的create_pivottable函数核心就一行:data.pivot_table(index=["day"], values=["tip"], columns=["time"], margins=True, aggfunc=sum)。我们来逐一拆解每个参数的作用。
index=["day"]:这相当于Excel透视表的“行”区域。我们把“星期几”(day)放在行上,结果中每一行代表一天(如Fri, Sat, Sun, Thur)。
values=["tip"]:这是我们关心的“值”,也就是我们要计算的数据,这里是“小费金额”(tip)。
columns=["time"]:这相当于Excel透视表的“列”区域。我们把“用餐时间”(time,如Dinner晚餐,Lunch午餐)放在列上。这样,行和列就构成了一个二维矩阵。
aggfunc=sum:这是聚合函数,指定我们对values里的数据做什么计算。这里是求和(sum),也就是计算每天每个时间段的小费总额。你也可以用mean、count、max等,或者像分组聚合那样传入一个函数列表。
margins=True:这是一个非常实用的参数。设置为True后,Pandas会在表格的最后一行和最后一列添加“总计”(All),分别计算每一行的总和与每一列的总和,以及右下角的总计。这对于快速查看整体情况非常有帮助。
运行后,你会得到一个结构清晰的表格:行是星期几,列是午餐和晚餐,表格中间的值就是对应的小费总额。最右边一列是每天的小费总计,最下面一行是午餐和晚餐各自的总计,右下角是所有小费的总和。一眼望去,哪一天、哪个时间段生意最好(小费最多),一目了然。
那么交叉表(crosstab)又是什么呢?它其实是透视表的一个特例,主要用于计算分组频率,也就是统计不同组合出现的次数。虽然这里用它来计算小费总和(通过values和aggfunc参数),但它最经典的用法是制作列联表。函数create_crosstab的调用是:pd.crosstab(index=[data.day], columns=[data.time], values=data.tip, aggfunc=sum, margins=True)。
你会发现它的参数和pivot_table非常像,但语法略有不同。crosstab的前两个参数直接就是index和columns(注意这里传入的是Series,所以用data.day),而values和aggfunc是让它能进行数值计算的关键。如果不指定values和aggfunc,crosstab默认计算的是频次(count)。比如pd.crosstab(data.day, data.time),得到的就是一张统计“每天每个时间段发生了多少笔交易”的计数表。
透视表和交叉表的选择,很多时候取决于个人习惯和场景。pivot_table是DataFrame的方法,更通用,功能也更全面(比如可以处理多层索引)。crosstab是顶层的函数,在专门制作频率表时语法更简洁直观。我个人的经验是,当需要快速做频次统计时用crosstab,当需要进行复杂的多维度数值聚合时用pivot_table。两者都掌握,你在面对多维数据时就能游刃有余。
3. 实战技巧深化:解决分组聚合中的常见陷阱
掌握了基础操作,我们得聊聊实战中肯定会遇到的“坑”。这些坑在头歌平台的简洁题目里可能不会出现,但在真实数据分析项目中,几乎百分之百会碰到。提前了解,能帮你省下大量调试时间。
第一个大坑:分组键的选择与数据类型。 groupby可以按列名分组,也可以按行索引分组,甚至可以按一个和DataFrame等长的Series或者数组分组。但最常见的问题是,你以为的“分类列”里混入了缺失值(NaN)或者意想不到的数据类型。比如,如果continent列里有个别行是空值,Pandas默认会把这些行单独分成一个以NaN为标签的组。这往往不是我们想要的。处理办法通常是在分组前先清洗数据:df = df.dropna(subset=['continent']),或者用fillna填充一个默认值。
另一个数据类型的问题是,如果分组键是数值型但实际代表类别(比如年份、月份编号),直接分组没问题,但有时为了后续处理方便,我们可能想先把它转成字符串类型:df['year_str'] = df['year'].astype(str),然后按year_str分组。这样可以避免一些因数值类型导致的排序或索引显示问题。
第二个大坑:聚合结果的索引重置。 使用groupby().agg()之后,得到的新DataFrame其分组键(如continent)会变成行索引(Index)。这在很多场景下是方便的,但有时我们希望它变回一个普通的列。比如,我们想把这个结果保存为CSV,或者方便地与其他表进行合并。这时候就需要reset_index()方法:result = df.groupby("continent").agg(mapping).reset_index()。这个小操作在数据流水线中极其高频,务必记住。
第三个大坑:对多列应用复杂聚合函数时的列名命名。 当我们像之前提到的,用字典对多列应用多个函数时,比如agg({"col1": ["mean", "std"], "col2": "sum"}),生成的列会是一个多级索引(MultiIndex)。第一层是原始列名,第二层是函数名。这虽然信息量大,但有时不利于后续处理或展示。我们可以用.columns属性来重命名,或者更优雅地在agg中使用元组来指定新列名(Pandas较新版本支持)。例如,一种常见的做法是:agg_result = df.groupby('g').agg( col1_mean=('col1', 'mean'), col1_std=('col1', 'std'), col2_sum=('col2', 'sum') )。这样生成的结果列名就是清晰易懂的col1_mean、col1_std和col2_sum。
第四个大坑:分组后过滤数据。 有时候,我们不是要对每个组做计算,而是想根据组的某些整体特征,筛选出符合条件的组。比如,我们只想保留那些记录数超过10条的洲的数据。这时候groupby().filter()方法就派上用场了。它的参数是一个函数,这个函数接收一个分组后的子DataFrame,返回True或False。例如:df_filtered = df.groupby('continent').filter(lambda x: len(x) > 10)。filter之后的数据,其原始索引和顺序都保持不变,只是不符合条件的组整个被移除了。这个功能在数据清洗阶段非常有用。
理解并避开这些陷阱,你的分组聚合操作就会从“能用”变得“稳健”。数据处理的大部分时间其实都花在解决这些边缘情况和数据异常上,核心的groupby和agg逻辑反而往往是最简单的部分。
4. 透视分析进阶:玩转多层索引与数据重塑
在第二关的透视表示例中,我们只用了index和columns各一个维度。但真实世界的数据往往是高维的。Pandas的透视表完全支持多层索引(MultiIndex),这让我们能在一个表格里洞察更多维度的信息。比如,我们除了想看“每天不同时间的小费”,还想加入“顾客性别”(sex)这个维度。我们可以这样写:pivot_result = data.pivot_table(index=["day", "sex"], values=["tip"], columns=["time"], aggfunc=sum)。
这时,行索引就变成了一个两层结构:第一层是day,第二层是sex。表格会先按天排列,在每一天下面再按性别(Male/Female)展开。这样的表格信息密度极高,能让我们同时分析日期、性别和时间段三个因素对小费的影响。当然,阅读起来也需要一点适应,你可能需要配合使用.stack()和.unstack()方法来调整表格的展示形态,让重点更突出。
说到.stack()和.unstack(),这是Pandas中数据重塑(Reshaping)的神器,它们经常和透视表的结果配合使用。简单来说,stack(堆叠)是把列索引的某一层“压”到行索引中去,让表格变“高”变“瘦”;而unstack(解堆叠)则是把行索引的某一层“提”到列索引中去,让表格变“宽”变“扁”。比如,我们对上面那个多层透视的结果执行pivot_result.unstack(level="sex"),就会把行索引中的sex这一层变成列索引,形成一个更宽的表。多尝试几次,你就能找到最适合当前分析需求的表格形态。
除了维度扩展,透视表的aggfunc参数也极其灵活。它可以接受一个函数列表,比如aggfunc=[np.sum, np.mean, len],这样会对values中的每一个指标同时计算总和、平均值和计数(样本数)。它还可以接受一个字典,为不同的values列指定不同的聚合函数,比如aggfunc={"tip": np.sum, "total_bill": np.mean}。这种灵活性让你几乎可以在一个操作内完成所有基础的描述性统计。
最后,别忘了透视表的fill_value参数。当某些行列组合没有数据时(比如周二可能没有午餐数据),默认会显示为NaN。如果你希望用0或其他值(比如-1)来填充这些缺失的组合,可以设置fill_value=0。这在生成报告或进行后续计算时,可以避免NaN值带来的麻烦。例如:data.pivot_table(..., aggfunc=sum, fill_value=0)。掌握这些进阶技巧,你就能把Pandas透视表变成一个真正的多维数据分析引擎,轻松应对各种复杂的业务报表需求。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐
所有评论(0)