【运营数据分析-基础篇】Pandas数据处理核心技能
【理论讲解】
Pandas是数据分析的瑞士军刀,它的强大之处在于能高效地处理表格数据。本章我们将学习如何对DataFrame进行各种操作,包括查看、选择、清洗、合并、分组和聚合。这些技能是进行任何数据分析的前提。
【提供数据集】
我们将使用一个模拟的电商订单数据集 ecommerce_orders.csv。请学员下载或自行创建此文件。
ecommerce_orders.csv 示例内容:
csv
order_id,user_id,product_id,product_name,category,price,quantity,order_date,payment_method,shipping_city
ORD001,U001,P101,T-shirt,Apparel,59.9,2,2023-01-01,Credit Card,Shanghai
ORD002,U002,P102,Jeans,Apparel,129.0,1,2023-01-01,Alipay,Beijing
ORD003,U001,P103,Sneakers,Footwear,399.0,1,2023-01-02,WeChat Pay,Shanghai
ORD004,U003,P101,T-shirt,Apparel,59.9,3,2023-01-02,Credit Card,Guangzhou
ORD005,U002,P104,Hat,Accessories,49.0,1,2023-01-03,Alipay,Beijing
ORD006,U004,P105,Backpack,Bags,250.0,1,2023-01-03,Credit Card,Shenzhen
ORD007,U001,P106,Watch,Accessories,800.0,1,2023-01-04,WeChat Pay,Shanghai
ORD008,U005,P101,T-shirt,Apparel,59.9,1,2023-01-04,Alipay,Chengdu
ORD009,U003,P107,Dress,Apparel,180.0,1,2023-01-05,Credit Card,Guangzhou
ORD010,U006,P108,Headphones,Electronics,350.0,1,2023-01-05,WeChat Pay,Hangzhou
ORD011,U001,P102,Jeans,Apparel,129.0,1,2023-01-06,Credit Card,Shanghai
ORD012,U007,P109,Keyboard,Electronics,450.0,1,2023-01-06,Alipay,Nanjing
ORD013,U008,P101,T-shirt,Apparel,59.9,2,2023-01-07,WeChat Pay,Wuhan
ORD014,U009,P110,Mouse,Electronics,80.0,1,2023-01-07,Credit Card,Suzhou
ORD015,U001,P104,Hat,Accessories,49.0,1,2023-01-08,Alipay,Shanghai
ORD016,U002,P111,Socks,Apparel,20.0,3,2023-01-08,WeChat Pay,Beijing
ORD017,U010,P101,T-shirt,Apparel,59.9,1,2023-01-09,Credit Card,Tianjin
ORD018,U003,P103,Sneakers,Footwear,399.0,1,2023-01-09,Alipay,Guangzhou
ORD019,U001,P101,T-shirt,Apparel,59.9,2,2023-01-10,Credit Card,Shanghai
ORD020,U002,P102,Jeans,Apparel,129.0,1,2023-01-10,Alipay,Beijing
2.1 DataFrame基本操作
【代码实例与电商场景案例】
python
import pandas as pd
# 加载电商订单数据
df = pd.read_csv('ecommerce_orders.csv')
print("原始数据预览 (前5行):")
print(df.head())
print("\n数据基本信息 (包含列名、非空值数量、数据类型):")
df.info()
print("\n数据统计摘要 (数值列的均值、标准差、最大值、最小值等):")
print(df.describe())
# 电商场景:查看最近的订单
print("\n查看最近的5条订单 (尾部数据):")
print(df.tail())
# 选择数据
# 电商场景:只查看订单ID和商品名称
print("\n选择特定列 (订单ID和商品名称):")
print(df[['order_id', 'product_name']].head())
# 电商场景:选择特定行(例如,前3条订单)
print("\n选择特定行 (前3条订单):")
print(df.iloc[0:3]) # 使用整数位置索引
# 电商场景:选择某个用户(U001)的所有订单
print("\n选择用户U001的所有订单:")
print(df[df['user_id'] == 'U001'])
# 电商场景:选择价格高于100元的订单
print("\n选择价格高于100元的订单:")
print(df[df['price'] > 100])
# 组合条件选择
# 电商场景:选择用户U001且价格高于100元的订单
print("\n选择用户U001且价格高于100元的订单:")
print(df[(df['user_id'] == 'U001') & (df['price'] > 100)])
# 数据排序
# 电商场景:按订单日期降序排列,查看最新订单
print("\n按订单日期降序排列的订单 (最新订单在前):")
print(df.sort_values(by='order_date', ascending=False).head())
# 电商场景:按商品类别和价格升序排列
print("\n按商品类别和价格升序排列的订单:")
print(df.sort_values(by=['category', 'price']).head())
# 数据排名 (Rank)
# 电商场景:给商品价格进行排名
df['price_rank'] = df['price'].rank(ascending=False) # 价格越高,排名越靠前
print("\n添加价格排名列:\n", df.sort_values(by='price_rank').head())
【互动问答】
df.head()和df.info()在数据探索初期各有什么用途?- 如何选择DataFrame中的单列?如何选择多列?
iloc和loc在选择行和列时有什么区别?- 如何实现复杂的数据筛选,例如筛选出某个城市、某个品类的商品?
sort_values()函数的by和ascending参数有什么作用?
2.2 数据清洗与预处理
【理论讲解】
数据清洗是数据分析中最耗时但也最重要的一步。脏乱的数据会导致错误的分析结果。我们将重点学习如何处理缺失值、异常值和重复值。
- 缺失值: 数据缺失了,就像拼图少了一块。我们需要决定是扔掉这块拼图,还是用其他方法补上。
- 异常值: 数据中“捣蛋鬼”,与其他数据格格不入。它们可能是录入错误,也可能是特殊情况,需要谨慎处理。
- 重复值: “双胞胎”数据,占用空间并可能误导分析。
【提供数据集】
为了演示数据清洗,我们创建一个包含缺失值、异常值和重复值的模拟数据集 dirty_ecommerce_data.csv。
dirty_ecommerce_data.csv 示例内容:
csv
order_id,user_id,product_name,price,quantity,order_date,category,rating
ORD001,U001,T-shirt,59.9,2,2023-01-01,Apparel,4.5
ORD002,U002,Jeans,129.0,1,2023-01-01,Apparel,4.0
ORD003,U001,Sneakers,399.0,1,2023-01-02,Footwear,5.0
ORD004,U003,T-shirt,59.9,3,2023-01-02,Apparel,NaN
ORD005,U002,Hat,49.0,1,2023-01-03,Accessories,3.5
ORD006,U004,Backpack,250.0,1,2023-01-03,Bags,4.8
ORD007,U001,Watch,800.0,1,2023-01-04,Accessories,NaN
ORD008,U005,T-shirt,59.9,1,2023-01-04,Apparel,4.2
ORD009,U003,Dress,180.0,1,2023-01-05,Apparel,4.1
ORD010,U006,Headphones,3500.0,1,2023-01-05,Electronics,4.7 # 异常值:价格过高
ORD011,U001,Jeans,129.0,1,2023-01-06,Apparel,4.0
ORD012,U007,Keyboard,450.0,1,2023-01-06,Electronics,4.3
ORD013,U008,T-shirt,59.9,2,2023-01-07,Apparel,4.6
ORD014,U009,Mouse,80.0,1,2023-01-07,Electronics,4.0
ORD015,U001,Hat,49.0,1,2023-01-08,Accessories,3.5 # 重复值(与ORD005相似,假设是重复订单)
ORD016,U002,Socks,20.0,3,2023-01-08,Apparel,3.8
ORD017,U010,T-shirt,59.9,1,2023-01-09,Apparel,4.5
ORD018,U003,Sneakers,399.0,1,2023-01-09,Footwear,5.0
ORD019,U001,T-shirt,59.9,2,2023-01-10,Apparel,4.5
ORD020,U002,Jeans,129.0,1,2023-01-10,Apparel,4.0
ORD021,U001,T-shirt,59.9,2,2023-01-01,Apparel,4.5 # 重复值(与ORD001完全一致)
【代码实例与电商场景案例】
python
import pandas as pd
import numpy as np
from sklearn.impute import SimpleImputer # 用于高级缺失值处理
# 加载脏数据
df_dirty = pd.read_csv('dirty_ecommerce_data.csv')
print("原始脏数据预览:\n", df_dirty)
print("\n数据信息:\n")
df_dirty.info()
# --- 缺失值处理 ---
print("\n--- 缺失值处理 ---")
# 1. 检测缺失值
print("\n每列的缺失值数量:\n", df_dirty.isnull().sum())
print("\n整个DataFrame是否有缺失值:", df_dirty.isnull().any().any()) # 快速判断是否有缺失
# 2. 删除缺失值
# 电商场景:如果订单的核心信息(如订单ID、商品名称)缺失,该订单可能无效,直接删除。
df_dropna = df_dirty.dropna(subset=['product_name', 'order_id']) # 删除product_name或order_id有缺失的行
print("\n删除特定列缺失值后的数据行数:", len(df_dropna))
# 3. 填充缺失值
# 电商场景:商品评分(rating)缺失,可以用平均评分或中位数填充,或者用0表示未评分。
# 填充前,先看看rating列的统计信息
print("\n填充前rating列的描述性统计:\n", df_dirty['rating'].describe())
# 方法一:用平均值填充 'rating' 列的缺失值
df_filled_mean = df_dirty.copy()
mean_rating = df_filled_mean['rating'].mean()
df_filled_mean['rating'].fillna(mean_rating, inplace=True)
print(f"\n用平均值 ({mean_rating:.2f}) 填充rating列缺失值后的前几行:\n", df_filled_mean.head(8))
# 方法二:用中位数填充 'rating' 列的缺失值
df_filled_median = df_dirty.copy()
median_rating = df_filled_median['rating'].median()
df_filled_median['rating'].fillna(median_rating, inplace=True)
print(f"\n用中位数 ({median_rating:.2f}) 填充rating列缺失值后的前几行:\n", df_filled_median.head(8))
# 方法三:用固定值填充 (例如,0表示未评分)
df_filled_zero = df_dirty.copy()
df_filled_zero['rating'].fillna(0, inplace=True)
print("\n用0填充rating列缺失值后的前几行:\n", df_filled_zero.head(8))
# 方法四:用前一个有效值填充 (forward fill)
df_filled_ffill = df_dirty.copy()
df_filled_ffill['rating'].fillna(method='ffill', inplace=True)
print("\n用前一个有效值填充rating列缺失值后的前几行:\n", df_filled_ffill.head(8))
# 方法五:使用sklearn的SimpleImputer (更通用的方法,尤其在ML预处理中)
df_sklearn_impute = df_dirty.copy()
# 创建一个Imputer,用列的均值填充NaN
imputer = SimpleImputer(missing_values=np.nan, strategy='mean')
# fit_transform会返回Numpy数组,需要转换回DataFrame
df_sklearn_impute['rating'] = imputer.fit_transform(df_sklearn_impute[['rating']])
print("\n使用SimpleImputer用均值填充rating列缺失值后的前几行:\n", df_sklearn_impute.head(8))
# --- 异常值处理 ---
print("\n\n--- 异常值处理 ---")
# 异常值:价格(price)列中,ORD010的3500.0可能是一个异常值。
# 我们使用Z-score方法来检测。Z-score > 3 或 < -3 通常被认为是异常值。
# 计算price列的Z-score
df_clean = df_filled_mean.copy() # 基于已经填充好缺失值的DataFrame进行异常值处理
df_clean['price_zscore'] = np.abs((df_clean['price'] - df_clean['price'].mean()) / df_clean['price'].std())
print("\n价格Z-score:\n", df_clean[['price', 'price_zscore']].sort_values(by='price_zscore', ascending=False).head())
# 电商场景:识别价格Z-score大于3的异常订单
outliers = df_clean[df_clean['price_zscore'] > 3]
print("\n检测到的价格异常值订单:\n", outliers)
# 处理异常值:删除异常值所在的行
df_no_outliers = df_clean[df_clean['price_zscore'] <= 3].drop(columns=['price_zscore'])
print("\n删除价格异常值后的数据行数:", len(df_no_outliers))
print("删除价格异常值后的数据预览:\n", df_no_outliers.sort_values(by='price', ascending=False).head())
# 另一个处理异常值的方法:替换为中位数或上限/下限
# 例如,我们将超过某个阈值的价格替换为该阈值
# price_upper_bound = df_clean['price'].quantile(0.99) # 99%分位数作为上限
# df_clean['price'] = np.where(df_clean['price'] > price_upper_bound, price_upper_bound, df_clean['price'])
# --- 重复值处理 ---
print("\n\n--- 重复值处理 ---")
# 1. 检测重复值
# 电商场景:完全相同的订单记录可能是重复录入,需要识别。
# ORD001 和 ORD021 完全一样,ORD005 和 ORD015 除了ID和日期,其他信息也高度相似。
print("\n检测完全重复的行:\n", df_dirty.duplicated())
print("\n完全重复的行数量:", df_dirty.duplicated().sum())
# 2. 删除重复值
# 默认删除所有列都重复的行,只保留第一次出现的
df_no_duplicates_all = df_dirty.drop_duplicates()
print("\n删除完全重复行后的数据行数:", len(df_no_duplicates_all))
# 电商场景:可能订单ID不同,但商品、用户、数量、价格都一样,我们认为这类是重复订单。
# 我们可以指定检查哪些列的重复
df_no_duplicates_subset = df_dirty.drop_duplicates(subset=['user_id', 'product_name', 'price', 'quantity'])
print("\n删除根据特定列判断的重复行后的数据行数:", len(df_no_duplicates_subset))
print("特定列去重后的数据预览:\n", df_no_duplicates_subset)
# --- 数据类型转换 ---
print("\n\n--- 数据类型转换 ---")
# 电商场景:order_date 通常是字符串,需要转换为日期时间类型才能进行日期计算。
df_processed = df_no_outliers.copy() # 基于之前处理好的数据
print("\n转换前 'order_date' 列的数据类型:", df_processed['order_date'].dtype)
df_processed['order_date'] = pd.to_datetime(df_processed['order_date'])
print("转换后 'order_date' 列的数据类型:", df_processed['order_date'].dtype)
print("转换后 'order_date' 列的前几行:\n", df_processed['order_date'].head())
# 可以提取年份、月份、星期几等
df_processed['order_year'] = df_processed['order_date'].dt.year
df_processed['order_month'] = df_processed['order_date'].dt.month
df_processed['order_day_of_week'] = df_processed['order_date'].dt.day_name()
print("\n添加日期特征后的数据预览:\n", df_processed.head())
【互动问答】
- 为什么在处理缺失值之前要先查看
df.info()和df.isnull().sum()? - 在电商场景中,你觉得哪些数据列的缺失值可以删除,哪些更适合填充?为什么?
- 除了Z-score,还有哪些方法可以检测异常值?(提示:IQR,箱线图)
df.drop_duplicates()默认的行为是什么?如何改变它的行为?- 为什么要把日期字符串转换为日期时间类型?转换后能做什么?
2.3 数据合并与连接
【理论讲解】
在实际的电商运营中,数据往往分散在不同的表格中,比如订单数据、商品详情数据、用户数据等。数据合并与连接就是将这些分散的数据整合起来,形成一张更完整、更有用的表格。
- concat: 简单地将DataFrame堆叠在一起(行堆叠或列堆叠)。
- merge: 基于一个或多个共同的“键”将两个DataFrame连接起来,就像数据库中的JOIN操作。
- 内连接 (inner join): 只保留两个表中都有匹配键的行。
- 左连接 (left join): 保留左表的所有行,并匹配右表中的行,如果右表没有匹配则为缺失值。
- 右连接 (right join): 保留右表的所有行,并匹配左表中的行,如果左表没有匹配则为缺失值。
- 外连接 (outer join): 保留两个表的所有行,不匹配的填充缺失值。
【提供数据集】
除了 ecommerce_orders.csv,我们再模拟一个 product_details.csv 文件,包含商品的更多信息。
product_details.csv 示例内容:
csv
product_id,product_description,brand,cost_price,supplier
P101,Comfortable cotton T-shirt,FashionBrand,30.0,SupplierA
P102,Classic straight-leg jeans,DenimStyle,70.0,SupplierB
P103,High-performance running sneakers,SportGear,200.0,SupplierC
P104,Stylish baseball cap,HeadWear,25.0,SupplierA
P105,Durable waterproof backpack,OutdoorLife,120.0,SupplierD
P106,Elegant smart watch,TechTime,400.0,SupplierE
P107,Summer floral dress,FashionBrand,90.0,SupplierB
P108,Noise-cancelling headphones,AudioPro,180.0,SupplierE
P109,Mechanical gaming keyboard,GameTech,220.0,SupplierC
P110,Ergonomic wireless mouse,GameTech,40.0,SupplierD
P111,Comfortable cotton socks,FashionBrand,10.0,SupplierA
【代码实例与电商场景案例】
python
import pandas as pd
# 加载订单数据和商品详情数据
df_orders = pd.read_csv('ecommerce_orders.csv')
df_products = pd.read_csv('product_details.csv')
print("订单数据预览:\n", df_orders.head())
print("\n商品详情数据预览:\n", df_products.head())
# --- concat: 简单堆叠 ---
print("\n--- concat: 简单堆叠 ---")
# 电商场景:如果你有来自不同时间段的订单数据,但结构完全一样,可以用concat堆叠起来。
# 这里我们模拟两个结构一样的订单子集进行堆叠
df_orders_part1 = df_orders.iloc[:5]
df_orders_part2 = df_orders.iloc[5:10]
df_combined_orders = pd.concat([df_orders_part1, df_orders_part2])
print("\n使用concat堆叠后的订单数据:\n", df_combined_orders)
# --- merge: 基于键连接 ---
print("\n\n--- merge: 基于键连接 ---")
# 电商场景:将订单数据与商品详情数据合并,以便分析订单中商品的成本、品牌等信息。
# 共同的键是 'product_id'
# 1. 内连接 (inner merge)
# 只保留在 df_orders 和 df_products 中都有 product_id 匹配的行
df_inner_merged = pd.merge(df_orders, df_products, on='product_id', how='inner')
print("\n内连接 (inner merge) 后的数据预览 (订单与商品详情):\n", df_inner_merged.head())
print("内连接后的数据行数:", len(df_inner_merged))
# 2. 左连接 (left merge)
# 保留 df_orders 的所有行,并匹配 df_products 中的信息。
# 如果 df_products 中没有对应的 product_id,则商品详情列为 NaN。
df_left_merged = pd.merge(df_orders, df_products, on='product_id', how='left')
print("\n左连接 (left merge) 后的数据预览:\n", df_left_merged.head())
print("左连接后的数据行数:", len(df_left_merged))
# 检查是否有未匹配的商品(即 product_details 中的列出现 NaN)
print("\n左连接后,检查是否有未匹配的商品(NaN值):\n", df_left_merged[df_left_merged['brand'].isnull()])
# 3. 右连接 (right merge)
# 保留 df_products 的所有行,并匹配 df_orders 中的信息。
# 如果 df_orders 中没有对应的 product_id,则订单详情列为 NaN。
df_right_merged = pd.merge(df_orders, df_products, on='product_id', how='right')
print("\n右连接 (right merge) 后的数据预览:\n", df_right_merged.head())
print("右连接后的数据行数:", len(df_right_merged))
# 检查是否有未被购买的商品
print("\n右连接后,检查是否有未被购买的商品(NaN值):\n", df_right_merged[df_right_merged['order_id'].isnull()])
# 4. 外连接 (outer merge)
# 保留 df_orders 和 df_products 的所有行,不匹配的填充 NaN。
df_outer_merged = pd.merge(df_orders, df_products, on='product_id', how='outer')
print("\n外连接 (outer merge) 后的数据预览:\n", df_outer_merged.head())
print("外连接后的数据行数:", len(df_outer_merged))
# 电商场景:计算每个订单的毛利润
# 假设销售价格是 df_orders['price'],成本价格是 df_products['cost_price']
# 我们需要先合并数据
df_merged_for_profit = pd.merge(df_orders, df_products[['product_id', 'cost_price']], on='product_id', how='left')
df_merged_for_profit['total_revenue'] = df_merged_for_profit['price'] * df_merged_for_profit['quantity']
df_merged_for_profit['total_cost'] = df_merged_for_profit['cost_price'] * df_merged_for_profit['quantity']
df_merged_for_profit['gross_profit'] = df_merged_for_profit['total_revenue'] - df_merged_for_profit['total_cost']
print("\n计算毛利润后的数据预览 (新增总收入、总成本、毛利润):\n", df_merged_for_profit[['order_id', 'product_name', 'price', 'quantity', 'cost_price', 'total_revenue', 'total_cost', 'gross_profit']].head())
【互动问答】
pd.concat()和pd.merge()的主要区别是什么?什么时候用哪个?merge()函数中的on参数有什么作用?how参数中的'inner','left','right','outer'分别代表什么意思?在电商场景中,你会在什么情况下选择哪种连接方式?- 如何处理合并后出现的重复列名(如果两个表中都有同名但含义不同的列)?(提示:
suffixes参数)
2.4 数据分组与聚合
【理论讲解】
数据分组与聚合是电商数据分析中非常重要的技能。它能帮助我们从海量数据中提取出有意义的统计信息,比如:哪个商品类别卖得最好?哪个城市的销售额最高?哪个用户消费金额最多?
- groupby: 按照一个或多个列的值将DataFrame分成若干个组。
- agg: 对每个组应用一个或多个聚合函数(如求和、平均值、计数、最大值、最小值等)。
【代码实例与电商场景案例】
我们将继续使用合并后的电商订单数据。
python
import pandas as pd
# 加载电商订单数据
df_orders = pd.read_csv('ecommerce_orders.csv')
# 确保order_date是日期时间类型,以便后续按日期分组
df_orders['order_date'] = pd.to_datetime(df_orders['order_date'])
# 计算每个订单的总金额
df_orders['total_amount'] = df_orders['price'] * df_orders['quantity']
print("处理后的订单数据预览:\n", df_orders.head())
# --- groupby: 分组 ---
print("\n--- groupby: 分组与聚合 ---")
# 电商场景:统计每个商品类别的总销售额
sales_by_category = df_orders.groupby('category')['total_amount'].sum()
print("\n每个商品类别的总销售额:\n", sales_by_category.sort_values(ascending=False))
# 电商场景:统计每个用户的订单数量和总消费金额
user_stats = df_orders.groupby('user_id').agg(
order_count=('order_id', 'count'), # 计算订单数量
total_spent=('total_amount', 'sum'), # 计算总消费金额
avg_price_per_item=('price', 'mean') # 计算平均商品单价
)
print("\n每个用户的订单数量和总消费金额:\n", user_stats.sort_values(by='total_spent', ascending=False).head())
# 电商场景:按城市统计平均订单金额
avg_order_by_city = df_orders.groupby('shipping_city')['total_amount'].mean()
print("\n按城市统计的平均订单金额:\n", avg_order_by_city.sort_values(ascending=False))
# 电商场景:按日期统计每日销售额和订单数量
daily_sales = df_orders.groupby('order_date').agg(
daily_revenue=('total_amount', 'sum'),
daily_orders=('order_id', 'count')
)
print("\n每日销售额和订单数量:\n", daily_sales.head())
# 多列分组
# 电商场景:统计每个城市不同支付方式的销售额
sales_by_city_payment = df_orders.groupby(['shipping_city', 'payment_method'])['total_amount'].sum().unstack()
print("\n每个城市不同支付方式的销售额:\n", sales_by_city_payment)
# --- apply: 应用自定义函数 ---
print("\n\n--- apply: 应用自定义函数 ---")
# 电商场景:为每个用户打上消费等级标签(例如,总消费金额前20%为“高价值用户”)
# 步骤1: 定义一个函数来判断消费等级
def assign_customer_segment(total_spent):
if total_spent >= user_stats['total_spent'].quantile(0.8): # 超过80%分位数
return '高价值用户'
elif total_spent >= user_stats['total_spent'].quantile(0.5): # 超过50%分位数
return '中价值用户'
else:
return '普通用户'
# 步骤2: 将函数应用到每个用户的总消费金额上
user_stats['customer_segment'] = user_stats['total_spent'].apply(assign_customer_segment)
print("\n添加用户消费等级后的用户统计:\n", user_stats.sort_values(by='total_spent', ascending=False).head())
# 电商场景:计算每个订单中,商品单价是否高于该类别平均单价
# 先计算每个类别的平均单价
avg_price_by_category = df_orders.groupby('category')['price'].mean().rename('avg_category_price')
# 将平均单价合并回原始订单数据
df_merged_avg_price = pd.merge(df_orders, avg_price_by_category, on='category', how='left')
df_merged_avg_price['is_above_category_avg'] = df_merged_avg_price['price'] > df_merged_avg_price['avg_category_price']
print("\n判断商品单价是否高于其类别平均单价:\n", df_merged_avg_price[['product_name', 'category', 'price', 'avg_category_price', 'is_above_category_avg']].head())
【互动问答】
groupby()后直接跟sum()和agg()有什么区别?什么时候用agg()更有优势?- 如何统计每个商品类别的独立用户数量?
unstack()函数有什么作用?在什么场景下会使用它?apply()函数在数据处理中有什么强大的作用?你能想出其他电商场景中使用apply()的例子吗?- 如何计算每个用户在第一次购买后,第二次购买的间隔时间?(这需要结合日期时间操作和分组)