想象一下,你面对一堆杂乱无章的快递包裹——里面可能有衣物、电子产品、日用品,甚至未拆封的文件。如果不进行分类,你就很难快速找到所需物品;而如果分类方式不合理(例如按“大小”而非“用途”),依旧会耗费大量时间。
大数据亦是如此。
原始数据往往极其混乱:百万级别的用户记录、千万条销售日志、亿级的行为轨迹……直接对这些数据进行分析,就如同在成堆的快递中寻找一件特定的衣服——几乎不可能完成。而分组统计,正是实现“分类整理”的关键步骤:它将海量信息按照特定维度(如地区、性别、时间)划分为多个小组,并计算每组的关键指标(如总和、均值、计数),从而让原本无序的数据变得清晰且有意义。
在大数据分析流程中,描述性分析是首要环节,用于回答“当前数据呈现出怎样的状态”。然而,在实际操作中,许多人常面临两大难题:
本文旨在系统性地解决上述挑战。无论你是数据分析师、产品经理,还是开发人员,都能从中掌握以下内容:
接下来,我们将从基础概念出发,逐步进阶,全面掌握分组统计的精髓——让你的数据从混沌走向有序,从无意义转化为高价值信息。
分组统计(Grouped Statistics)指的是依据一个或多个维度将数据集划分为若干子集,然后对每个子集应用聚合函数(Aggregate Function)进行汇总计算的过程。其基本结构可表示为:
分组统计 = 分组(Group By) + 聚合(Aggregate)
其中包含两个关键要素:
COUNT(计数)、
SUM(求和)、
AVG(均值)、
MEDIAN(中位数)、
MAX(最大值)
举例说明:假设我们拥有一份电商销售数据,目标是了解“不同地区的用户购买情况差异”,那么分组逻辑如下:
region(地区);COUNT(user_id)(用户数量)、AVG(sales)(平均客单价)、SUM(sales)(总销售额)。最终输出的结果可能如下表所示:
| region | user_count | avg_sales | total_sales |
|---|---|---|---|
| 南方 | 50000 | 150 | 7,500,000 |
| 北方 | 40000 | 120 | 4,800,000 |
| 西部 | 30000 | 100 | 3,000,000 |
相比原始数据,这样的呈现方式显然更加直观。我们可以迅速得出结论:南方地区不仅用户最多,平均消费水平也最高,贡献了接近45%的总销售额——这正是有价值的业务洞察。
描述性分析的核心任务是“总结数据特征”,而分组统计正是达成这一目标不可或缺的方法。其主要价值体现在三个方面:
选择合适的分组维度取决于具体的分析目的。以下是几类高频使用的分类标准:
尽管可用于分组统计的工具有多种,但最广泛使用的仍是SQL(关系型数据库查询语言)、Pandas(Python数据分析库)和Spark(分布式大数据处理框架)。下面我们分别介绍它们在实际场景中的应用技巧。
作为分组统计领域的经典工具,SQL被绝大多数数据从业者所熟知。其核心语法为
,通常结合GROUP BY
与聚合函数
(用于筛选分组后的结果)共同使用。HAVING
以统计“各地区用户数量及平均销售额”为例,SQL语句如下:
SELECT region, -- 分组键:地区 COUNT(DISTINCT user_id) AS user_count, -- 聚合函数:去重统计用户数 AVG(sales) AS avg_sales -- 聚合函数:计算人均销售额 FROM sales_data -- 数据源:销售数据表 GROUP BY region; -- 按地区分组
在数据分析中,分组操作是提取关键洞察的重要手段。以下是关于SQL与Pandas中分组统计的详细说明与优化重构内容。
使用 GROUP BY 可以按指定字段对数据进行归类汇总。例如,以下语句按地区(region)进行分组,并计算各地区的订单数量和总销售额:
SELECT
region, -- 分组维度:地区
COUNT(DISTINCT order_id) AS order_count,
SUM(sales) AS total_sales
FROM
sales_data
GROUP BY
region
ORDER BY
avg_sales DESC; -- 按平均销售额降序排列结果
若需更细粒度分析,如“地区+性别”组合维度下的用户行为,可采用多字段分组方式:
SELECT
region, -- 第一分组键:地区
gender, -- 第二分组键:性别
COUNT(DISTINCT order_id) AS order_count,
SUM(sales) AS total_sales
FROM
sales_data
GROUP BY
region, gender
HAVING
total_sales > 1000000; -- 筛选聚合后满足条件的组
DISTINCT
COUNT(DISTINCT user_id)HAVINGWHEREORDER BYSQL 中的 GROUP BY 默认会忽略 NULL 值,即含有 NULL 的记录不会参与任何分组。这可能导致部分数据缺失。解决方法包括:
COALESCE() 函数将其替换为有意义的默认标签,如“未知地区”;COALESCEWHERE region IS NOT NULL 移除空值记录;WHERE region IS NOT NULL示例代码如下:
SELECT
COALESCE(region, '未知地区') AS region,
COUNT(DISTINCT user_id) AS user_count
FROM
sales_data
GROUP BY
COALESCE(region, '未知地区');
GROUP BY
region
Pandas 提供了强大的 .groupby() 方法,支持灵活的数据聚合与转换操作,尤其适合复杂逻辑和自定义函数的应用场景。
groupby
以计算“各地区用户数及平均销售额”为例:
import pandas as pd
# 加载数据(假设存储为CSV)
df = pd.read_csv('sales_data.csv')
# 按“region”字段分组并聚合
grouped = df.groupby('region').agg(
user_count=('user_id', 'nunique'), # 统计唯一用户数
avg_sales=('sales', 'mean') # 计算销售均值
)
# 将索引转为普通列
grouped = grouped.reset_index()
# 按平均销售额从高到低排序
grouped = grouped.sort_values(by='avg_sales', ascending=False)
print(grouped.head()) # 输出前5行
从 Pandas 0.25 版本起,支持使用元组形式为聚合结果自定义列名,提升可读性。例如,按“地区+性别”进行分组并计算多个指标:
# 多维度分组 + 自定义输出列名
grouped = df.groupby(['region', 'gender']).agg(
订单数=('order_id', 'count'),
平均客单价=('sales', 'mean'),
总销售额=('sales', 'sum')
)
# 恢复索引以便后续处理
grouped = grouped.reset_index()
print(grouped)
named aggregation
| region | gender | 订单数 | 平均客单价 | 总销售额 |
|---|---|---|---|---|
| 南方 | 女 | 12000 | 150 | 1,800,000 |
| 南方 | 男 | 8000 | 130 | 1,040,000 |
| 北方 | 女 | 9000 | 120 | 1,080,000 |
transform() 是 Pandas 中一个极为实用但常被忽视的功能,它能将每组的聚合结果广播回原始数据形状,实现逐行比较。
transform
例如,判断每个订单的销售额是否高于其所在地区的平均水平:
# 计算各地区平均销售额并映射回原表
df['region_avg'] = df.groupby('region')['sales'].transform('mean')
# 添加判断列:是否高于地区均值
df['above_avg'] = df['sales'] > df['region_avg']
print(df[['user_id', 'region', 'sales', 'region_avg', 'above_avg']].head())
该方法避免了手动合并操作,极大提升了效率与代码简洁性。
当处理大规模数据时,分组统计是数据分析中的核心操作。在不同场景下,我们可以使用不同的工具与策略来实现高效计算。
例如,在Pandas中可以通过以下方式计算每个地区用户的平均销售额,并判断个体是否高于该均值:
df['region_avg_sales'] = df.groupby('region')['sales'].transform('mean')
# 判断每个用户的销售额是否高于所在地区的平均水平
df['is_above_avg'] = df['sales'] > df['region_avg_sales']
print(df[['user_id', 'region', 'sales', 'region_avg_sales', 'is_above_avg']].head())
输出示例为:
| user_id | region | sales | region_avg_sales | is_above_avg |
|---|---|---|---|---|
| 1001 | 南方 | 180 | 140 | True |
| 1002 | 南方 | 120 | 140 | False |
| 1003 | 北方 | 130 | 115 | True |
通过这种方式,可以快速识别出“高价值用户”,比如南方地区销售额超过140的用户群体。
当数据量达到百GB以上级别时,Pandas将面临内存限制,此时应转向分布式框架——Spark。作为支持TB至PB级数据处理的计算平台,Spark提供了类似SQL和Pandas的分组语法,同时具备更高的可扩展性,但需注意性能调优。
Spark允许使用标准SQL语句执行分析任务,其语法结构与传统数据库高度相似:
from pyspark.sql import SparkSession
# 创建Spark会话实例
spark = SparkSession.builder.appName("GroupByExample").getOrCreate()
# 读取Parquet格式的数据(相比CSV更节省空间且读取更快)
df = spark.read.parquet('sales_data.parquet')
# 注册临时视图以便执行SQL查询
df.createOrReplaceTempView('sales_data')
# 执行分组统计查询
result = spark.sql("""
SELECT
region,
gender,
COUNT(DISTINCT order_id) AS order_count,
AVG(sales) AS avg_sales,
SUM(sales) AS total_sales
FROM
sales_data
GROUP BY
region, gender
ORDER BY
total_sales DESC
""")
result.show()
相较于SQL,DataFrame API提供更强的编程灵活性,支持函数组合与自定义逻辑嵌入:
from pyspark.sql.functions import countDistinct, avg, sum
# 按区域和性别分组,计算多项指标
result = df.groupBy('region', 'gender') \
.agg(
countDistinct('order_id').alias('order_count'), # 去重订单数
avg('sales').alias('avg_sales'), # 平均销售金额
sum('sales').alias('total_sales') # 总销售额
) \
.orderBy('total_sales', ascending=False)
result.show()
在Spark中,分组操作通常引发shuffle过程
groupBy —— 即根据分组键重新分布数据到各节点,这一过程非常耗资源。为了提升效率,建议对大表预先按常用分组字段进行分区:
# 按"region"字段提前分区,使相同region的数据集中于同一节点
df_repartitioned = df.repartition('region')
# 后续分组操作将显著减少网络传输成本
result = df_repartitioned.groupBy('region') \
.agg(sum('sales').alias('total_sales')) \
.orderBy('total_sales', ascending=False)
result.show()
说明:预分区
repartition 虽然能优化后续分组性能,但本身涉及数据重分布,仅推荐用于超大规模数据集(如100GB以上)。对于小规模数据,反而可能带来额外开销,无需启用。
在掌握基本分组方法后,进一步应用高级技术可以帮助我们挖掘更复杂的业务规律。以下是三种常见的进阶分组模式:
嵌套分组(Nested Grouping)指的是按照多个层级维度依次进行分组统计,从而揭示数据内部的层次关系与趋势分布。
repartition在数据分析中,分层与分组是揭示数据内在规律的重要手段。通过不同维度的组合或基于分布、时间的划分方式,我们可以从复杂的数据中提取出有价值的洞察。
采用多个字段进行嵌套式分组,例如“地区→性别→星期几”,有助于识别更精细的行为趋势。以分析“南方地区不同性别用户在一周内销售额变化”为例:
# 按区域、性别和星期几(0=周一,6=周日)进行分组,并汇总销售总额
grouped = df.groupby(['region', 'gender', df['date'].dt.weekday]).agg(
total_sales=('sales', 'sum')
)
# 重置索引以便后续处理
grouped = grouped.reset_index()
# 将数字表示的星期转换为中文名称
grouped['weekday'] = grouped['weekday'].map({0: '周一', 1: '周二', ..., 6: '周日'})
# 按照区域和性别排序,便于查看结果
grouped = grouped.sort_values(by=['region', 'gender'])
print(grouped.head())
输出示例:
| region | gender | weekday | total_sales |
|---|---|---|---|
| 南方 | 女 | 周五 | 300,000 |
| 南方 | 女 | 周六 | 450,000 |
| 南方 | 女 | 周日 | 500,000 |
| 南方 | 男 | 周五 | 200,000 |
resample
从上述结果可以看出:南方女性用户的消费高峰集中在周末(周六、周日),而男性则在周五达到峰值。这表明女性更偏好周末购物,男性则倾向于工作日下班后消费。
滑动窗口技术适用于按时间区间聚合数据,如“每7天”或“过去7天”的滚动统计,广泛应用于销售趋势、用户活跃度等时序分析场景。
Pandas 实现方式:
首先确保日期列已转换为 datetime 类型:
df['date'] = pd.to_datetime(df['date'])
接下来可选择两种策略:
# 固定窗口:每7天一组,统计总销售额
weekly_sales = df.resample('7D', on='date').agg(total_sales=('sales', 'sum'))
# 滚动窗口:每日计算过去7天的销售额总和
rolling_weekly_sales = df.resample('D', on='date').agg(total_sales=('sales', 'sum')) \
.rolling(window=7).sum()
输出示例(固定窗口):
| date | total_sales |
|---|---|
| 2023-01-01 | 1,200,000 |
| 2023-01-08 | 1,500,000 |
| 2023-01-15 | 1,800,000 |
输出示例(滚动窗口):
| date | total_sales |
|---|---|
| 2023-01-01 | NaN |
| 2023-01-02 | NaN |
| ... | ... |
| 2023-01-07 | 1,200,000 |
| 2023-01-08 | 1,300,000 |
Spark 实现方式:
Spark 提供了更为灵活的窗口函数支持,可用于实现复杂的滑动逻辑。
Window
from pyspark.sql import Window
from pyspark.sql.functions import sum, col
# 定义窗口:按地区分区,按时间排序,范围为当前行及此前6天(共7天)
window = Window.partitionBy('region') \
.orderBy(col('date').cast('long')) \
.rangeBetween(-6*86400, 0) # 时间单位为秒
# 计算每个地区每天的近7天累计销售额
df_rolling = df.withColumn('rolling_7d_sales', sum('sales').over(window))
df_rolling.show()
说明:Spark 的窗口函数虽然语法较复杂,但其优势在于支持基于时间戳或行数的动态范围窗口,能够处理 Pandas 所不具备的高级场景,比如跨行偏移、非等距时间窗口等。
分位数分组(Quantile Grouping)是一种将数值型数据按照其分布划分为若干等级的方法,常见形式包括四分位(Q1-Q4)、十分位(D1-D10)等。该方法常用于用户价值分层,如识别高、中、低贡献用户。
在 Pandas 中可通过 qcut 函数实现:
# 示例:将用户按销售额分为四个等级(四分位) df['sales_quartile'] = pd.qcut(df['sales'], q=4, labels=['Q1', 'Q2', 'Q3', 'Q4'])
此操作会自动根据销售额的分布边界,将用户均匀地分配至四个区间,从而便于后续针对不同层级用户制定差异化运营策略。
1. 背景
在电商平台中,用户销售额分布通常呈现长尾特征——少数高价值用户贡献了大部分收入。为了精细化运营,需将用户按消费水平分层分析,识别不同层级用户的数量与平均表现。
qcut
该函数可依据数据的分位数将其划分为指定数量的组,确保每组包含大致相等的数据量(因基于分位点划分)。例如,若希望根据“销售额”将用户划分为4个等级:
# 将'sales'列按四分位切分,并标记为Q1(最低25%)至Q4(最高25%)
df['sales_quartile'] = pd.qcut(df['sales'], q=4, labels=['Q1', 'Q2', 'Q3', 'Q4'])
# 按生成的四分位组别聚合,统计各组用户数及平均销售额
grouped = df.groupby('sales_quartile').agg(
user_count=('user_id', 'nunique'),
avg_sales=('sales', 'mean')
)
print(grouped)
输出示例:
| sales_quartile | user_count | avg_sales |
|---|---|---|
| Q1 | 25000 | 50 |
| Q2 | 25000 | 100 |
| Q3 | 25000 | 150 |
| Q4 | 25000 | 200 |
qcut
说明:
参数 q 表示要划分的分位数组数(如 q=4 对应四分位),labels 参数用于定义每组的标签名称。
q
q=4
labels
Spark 并未提供直接等价于 qcut 的函数,但可通过 percent_rank 函数计算每个值的百分位排名,再手动划分区间完成分组:
from pyspark.sql.functions import percent_rank, when
from pyspark.sql.window import Window
# 计算每个用户销售额在整体中的百分位排名(0表示最低,1表示最高)
df_percent_rank = df.withColumn(
'percent_rank',
percent_rank().over(Window.orderBy('sales'))
)
# 根据百分位排名划分四分位组
df_quartile = df_percent_rank.withColumn(
'sales_quartile',
when(col('percent_rank') <= 0.25, 'Q1')
.when(col('percent_rank') <= 0.5, 'Q2')
.when(col('percent_rank') <= 0.75, 'Q3')
.otherwise('Q4')
)
# 按四分位组进行聚合统计
grouped = df_quartile.groupBy('sales_quartile').agg(
user_count=('user_id', 'nunique'),
avg_sales=('sales', 'mean')
)
grouped.show()
结果形式与 Pandas 类似,但 Spark 支持处理更大规模的数据(如 TB 级),适合分布式环境下的高性能计算。
percent_rank
尽管分组统计操作看似简单,但在实际应用中容易出现以下几类典型问题:
问题表现:分组粒度过细(如按“用户ID”分组),导致每组仅一条记录,失去统计意义;或粒度过粗(如仅按“国家”分组),掩盖内部差异(如中国南北方市场行为不同)。
应对策略:应结合具体分析目标合理设定分组维度。例如:
问题表现:对存在极端值的数据使用均值(mean),造成统计偏差。例如某地区100名用户中,99人年收入1万元,1人年收入100万元,则平均收入为1.99万元,远高于大多数人的实际水平;而中位数为1万元,更能反映典型情况。
解决方案:
mean];median];count] 或计算占比(频次/总数)。AVG
AVG
MEDIAN
COUNT
问题表现:分组字段存在 NULL 值时,不同工具处理机制不同:SQL 默认忽略 NULL 分组,而 Pandas 会将其视为独立一组,可能导致结果误解。
解决办法:
fillna()(Pandas)或 COALESCE()(SQL)将 NULL 替换为合理默认值(如“未知地区”);dropna()(Pandas)或 IS NOT NULL 过滤条件(SQL)排除 NULL 记录。GROUP BY
fillna
COALESCE
dropna
WHERE
问题表现:使用 Pandas 处理超大规模数据(如 TB 级)易引发内存溢出;而在小数据集上使用 Spark 反而导致额外开销(如 Shuffle 操作耗时)。
优化建议:
repartition],减少不必要的 join 或 shuffle 操作以提升执行效率。repartition
shuffle某电商平台希望探究“不同地区与性别用户的购买行为差异”,以支持个性化营销策略的制定。平台现有以下数据:
user_id
gender
region
order_id
user_id
date
sales
本次分析聚焦于三个核心指标,按地区和性别两个维度进行拆解:
将用户信息与订单记录通过用户ID进行关联,构建一个融合用户属性与交易行为的宽表结构:
# 加载用户数据和订单数据
user_df = pd.read_csv('user_data.csv')
order_df = pd.read_csv('order_data.csv')
# 基于 user_id 内连接合并两张表
df = pd.merge(user_df, order_df, on='user_id', how='inner')
利用Pandas对合并后的数据按“region”(地区)与“gender”(性别)组合进行分组,并统计所需指标:
# 分组并聚合关键指标
grouped = df.groupby(['region', 'gender']).agg(
购买次数=('order_id', 'count'),
平均客单价=('sales', 'mean'),
总销售额=('sales', 'sum')
)
# 恢复索引并按总销售额降序排列
grouped = grouped.reset_index().sort_values(by='总销售额', ascending=False)
| region | gender | 购买次数 | 平均客单价 | 总销售额 | 总销售额占比 |
|---|---|---|---|---|---|
| 南方 | 女 | 12000 | 150 | 1,800,000 | 35% |
| 南方 | 男 | 8000 | 130 | 1,040,000 | 20% |
| 北方 | 女 | 9000 | 120 | 1,080,000 | 21% |
| 北方 | 男 | 7000 | 110 | 770,000 | 15% |
| 西部 | 女 | 5000 | 100 | 500,000 | 9% |
洞察发现:
分组统计作为描述性数据分析的关键方法,能够将杂乱无章的原始数据转化为清晰可读的业务洞见,帮助回答诸如“当前数据呈现何种特征?”“不同用户群之间存在哪些差异?”等关键问题。本文重点涵盖:
GROUP BY)、Pandas(groupby)、Spark(groupBy)的实际操作技巧;
掌握分组统计能力,有助于从海量数据中高效提取有价值的信息,直接服务于各类业务决策场景:
立即动手实践!尝试使用文中介绍的方法分析你手中的真实数据:
欢迎在评论区分享你的分析成果或提出疑问——让我们共同探讨进步!
随着大数据技术持续演进,分组统计正朝着更智能、更实时的方向发展:
可以预见,未来的分组统计将成为驱动数据分析的核心引擎,更加自动化、智能化地赋能企业决策体系。
(此处保留作者介绍位置)
我是李阳,一名拥有8年以上行业经验的资深数据分析师,长期深耕于大数据分析与机器学习领域。曾为电商、金融、零售等多个行业提供定制化的数据解决方案,助力企业实现数据驱动决策。
我热衷于用简单明了的方式讲解复杂的数据知识,致力于让数据分析变得更易懂、更有趣,帮助更多人建立数据思维,真正爱上数据工作。
如果您对数据分析感兴趣,欢迎关注我的公众号“数据思维”,将定期分享实用的数据分析实战技巧与项目经验。
分组统计 = 分组(Group By) + 聚合(Aggregate)
备注:文中所展示的代码示例均为简化版本,实际应用时需结合具体数据情况进行相应调整,例如缺失值处理、数据类型转换等操作。若您在实践过程中遇到任何问题,欢迎在评论区留言交流,我会尽可能为大家提供解答与支持。
扫码加好友,拉您进群



收藏
