【Python数据分析】pandas apply自定义函数+分组操作+分析案例

文章目录

  • 1.apply()
    • [1.1函数 操作series对象](#1.1函数 操作series对象)
    • [1.2 apply()函数 => 操作DataFrame对象](#1.2 apply()函数 => 操作DataFrame对象)
    • [1.3 向量化函数](#1.3 向量化函数)
    • [1.4 apply()函数的案例-泰坦尼克号数据集](#1.4 apply()函数的案例-泰坦尼克号数据集)
    • [1.5 apply()函数 结合 lambda表达式使用.](#1.5 apply()函数 结合 lambda表达式使用.)
  • [2. 分组操作](#2. 分组操作)
    • [2.1 分组 + 聚合操作](#2.1 分组 + 聚合操作)
    • [2.2 分组 + 转换](#2.2 分组 + 转换)
    • [2.3 分组 + 过滤](#2.3 分组 + 过滤)
    • [2.4 DataFrameGroupby df的分组对象](#2.4 DataFrameGroupby df的分组对象)
  • [3. 分析案例](#3. 分析案例)

1.apply()

1.1函数 操作series对象

apply()函数操作Series对象, 是把Series的逐个值进行传入并操作的.

语法 : df对象.apply(函数对象,参数名(如果有) 参数1 ...)

python 复制代码
df = pd.DataFrame({'a': [10, 20, 30], 'b': [20, 30, 40]})
def my_fun1(x):
    print('看看我执行了嘛!')
    return x ** 2


# 扩展: 定义函数, 计算x的e次方.
def my_fun2(x, e):
    return x ** e
df['a'].apply(my_fun1)  # 细节: 这里写的是函数名, 即: 函数对象.  如果写: 函数名() 则表示是在调用函数.

df.a.apply(my_fun2, e=3)  # 细节: 传参数时, 使用 关键字参数 写法进行传参.

1.2 apply()函数 => 操作DataFrame对象

DataFrame 的 apply() 默认是传入整列的 而不是 逐个值进行传入的

语法 df对象.apply(函数对象,参数名(如果有) 参数1 ...)

python 复制代码
# df 的 apply() 默认是传入整列的 而不是 逐个值进行传入的
def my_fun3(col):
    x = col[0]
    y = col[1]
    z = col[2]
    print(col)
    print(type(col))
    return (x + y + z)/3

def my_fun4(col):
    return col.mean()
df.apply(my_fun3)
df.apply(my_fun4)

1.3 向量化函数

当 函数 传入的 是 向量 即 需要手动遍历输出的时候 可以使用 np.vectorize 装饰器自动遍历

python 复制代码
@np.vectorize
def my_fun6(x, y):
    # 判断, 如果x的值是20, 就返回NaN
    if x == 20:         # 报错: x是向量, 20是标量, 向量和标量无法直接计算. 
        return np.NAN
    
    # for i in x:
    #     if i == 20:         # 手动遍历, 就不报错了, 但是结果不是我们要的.
    #         return np.NAN
    
    # x代表第1列数据, y代表第2列数据
    return (x + y) / 2

# 调用函数
my_fun6(df.a, df.b)
# 传统写法.
my_fun6 = np.vectorize(my_fun6)     # 装饰后的函数对象 = 装饰器(要被装饰的函数名)
my_fun6(df.a, df.b)

1.4 apply()函数的案例-泰坦尼克号数据集

  • 需求1: 计算泰坦尼克号数据中, 每列的null值总数, 缺失占比, 非缺失值占比.
python 复制代码
# 1. 加载数据集, 获取df对象.
train = pd.read_csv('data/titanic_train.csv')
# 1. 定义函数 count_missing(), 计算每列的缺失值总数
def count_missing(col):           # col => 每列数据, Series对象
    return col.isnull().sum()

# 2. 定义函数 prop_missing(), 计算每列的缺失值占比.
def prop_missing(col):
    # 缺失值占比 = 缺失值数量 / 该列总长度
    # return count_missing(col) / len(col)
    return count_missing(col) / col.size

# 3. 定义函数 prop_not_missing(), 计算每列的非缺失值占比.
def prop_not_missing(col):
    # 非缺失值占比 = 1 - 缺失值占比
    return 1 - prop_missing(col)

# 4. 调用上述的函数, 获取结果.
train.apply(count_missing)      # 获取每列的缺失值总数
train.apply(prop_missing)       # 获取每列的缺失值占比 
train.apply(prop_not_missing)   # 获取每列的非缺失值占比 
  • 需求2: 计算泰坦尼克号数据中, 各年龄段总人数.
python 复制代码
# 方式1: 直接算每个年龄出现了多少次, 即: 每个年龄的总人数, 但是达不到我们要的效果.
train.Age.value_counts()
# 解题思路: 把年龄变成年龄段的值, 然后再进行统计.
# 1. 定义函数, 接收年龄, 将其转成年龄段. 
def cut_age(age):
    if 0 <= age < 18:
        return '未成年'
    elif 18 <= age < 40:
        return '青年'
    elif 40 <= age < 60:
        return '壮年'
    elif 60 <= age < 80:
        return '老年'
    else:
        return '未知'
    
# 2. 把上述的函数, 作用于Age列, 得到新的列, 计算结果即可.
train.Age.apply(cut_age)
train.Age.apply(cut_age).value_counts()
  • 需求3: 统计VIP 和 非VIP的客户总数
python 复制代码
# 1. 定义函数, 用于判断是否是VIP用户.
def is_vip(row):
    if row.Pclass == 1 and ('Master' in row.Name or 'Dr' in row.Name or 'Sir' in row.Name):
        return 'VIP'
    else:
        return '非VIP'
    
train.apply(is_vip, axis=1)     # 传入整行数据
train.apply(is_vip, axis=1).value_counts()

1.5 apply()函数 结合 lambda表达式使用.

python 复制代码
# 细节: 如果需求比较简单, 没有必要重新定义1个新的函数, 可以直接传入Lambda表达式.
# 1. 定义数据集.
df = pd.DataFrame({'a': [10, 20, 30], 'b': [20, 30, 40]})
python 复制代码
# 2. 需求: 每个值 => 该值的平方.
def my_fun1(x):
    return x ** 2

df.apply(my_fun1)
python 复制代码
# 3. 上述的需求可以用 Lambda表达式来完成.
df.apply(lambda x : x ** 2)
df.apply(lambda x : x.mean())
df.apply(lambda x : x.mean(), axis=0)   # 效果同上.

df.apply(lambda x : x.mean(), axis=1)   # 统计每行的平均值

2. 分组操作

2.1 分组 + 聚合操作

python 复制代码
# 1. 读取数据, 获取df对象
df = pd.read_csv('data/gapminder.tsv', sep='\t')
df.head()
  • 语法 df对象.groupby('要分组的字段')'要操作的字段'.聚合函数()
python 复制代码
# 写法1
df.groupby('year')['lifeExp'].mean()
# 写法2
df.groupby('year').lifeExp.mean()

# 上述都是一步到位, 直接计算结果, 我们也可以手动计算. 
# 1. 我们先看看一共有多少个年
df.year.unique()  # 12个年份, 底层算 12 次即可, 这里我们就用 1952年举例.

# 2. 获取1952年所有的数据, 计算平均寿命
df[df['year'] == 1952].lifeExp.mean()
df[df.year == 1952].lifeExp.mean()  # 效果同上.
# 计算各个大洲平均寿命.
# 写法1
df.groupby('continent')['lifeExp'].mean()

# 分组之后, 也可以用 describe()同时计算多个统计量.
df.groupby('continent')['lifeExp'].describe()

df.groupby('continent')['lifeExp'].mean()
df.groupby('continent')['lifeExp'].agg('mean')  # 这里的mean是: pandas的函数
# df.groupby('continent')['lifeExp'].agg(np.mean)  # 这里的mean是: Numpy的函数

df.groupby('continent')['lifeExp'].aggregate('mean')  # 效果同上.
# 需求3: 上述的写法, 也可以改造成 agg() 或者 aggregate()函数, 效果一致
# 计算各个大洲平均寿命.
df.groupby('continent')['lifeExp'].mean()
df.groupby('continent')['lifeExp'].agg('mean')  # 这里的mean是: pandas的函数
# df.groupby('continent')['lifeExp'].agg(np.mean)  # 这里的mean是: Numpy的函数

df.groupby('continent')['lifeExp'].aggregate('mean')  # 效果同上.
#需求4: 给 agg()或者 aggregate传入字典
# 需求: 统计各个大洲 平均寿命, 人口的中位数, 最大GDP
df.groupby('continent').agg({'lifeExp': 'mean', 'pop': 'median', 'gdpPercap': 'max'})
df.groupby('continent').aggregate({'lifeExp': 'mean', 'pop': 'median', 'gdpPercap': 'max'})  # 效果同上

# 语法糖, 如果聚合函数一样, 则可以简写成如下操作, 例如: 各个大洲平均寿命, 平均人口, 平均GDP
df.groupby('continent').agg({'lifeExp': 'mean', 'pop': 'mean', 'gdpPercap': 'mean'})
df.groupby('continent')[['lifeExp', 'pop', 'gdpPercap']].mean()

2.2 分组 + 转换

复制代码
需求1: 计算x的 z-score分数, 也叫: 标准分数,  公式为: (x - x_mean) / x_std
pyt'ho 复制代码
# 2. 定义函数, 计算某列的 z-score分数.
def my_zscore(col):
    return (col - col.mean()) / col.std()  # (列值 - 平均值) / 标准差
# 3. 调用上述的格式.
df.groupby('year').lifeExp.apply(my_zscore)  # 1704条
# 4. 查看原始df的数据集总数.
df  # 结论: 分组 + 转换处理后, 数据集总数不变.
复制代码
# 需求: 读取文件(小票信息), 获取df对象. 其中有1列 total_bill 表示总消费. 随机抽取4个缺失值, 然后进行填充. 
# 填充方式: 每个组的平均值. 即: 如果是Male => 就用 Male列的平均值填充, 如果是Female => Female列的平均值填充.
# 1. 读取文件, 获取DataFrame对象
df = pd.read_csv('data/tips.csv')
df

# 2. 抽样方式, 从上述的df对象中, 随机抽取10条数据. 
# tips_10 = df.sample(10)     # 这里的10表示随机抽取 10 条数据.
# random_state: 随机种子, 只要种子一样, 每次抽取的数值都是一样的. 
tips_10 = df.sample(10, random_state=21)
tips_10

# 3. 随机的从上述的10条数据中, 抽取4行数据, 设置他们的 total_bill(消费总金额) 为 NaN
# 写法1: 每次固定 这四条数据 的 total_bill为 空值.
# tips_10.loc[[173, 240, 243, 175], 'total_bill'] = np.NaN

# 写法2: 每次随机4条数据, 设置它们的 total_bill为 空值.
# np.random.permutation()解释: 随机打乱索引值, 并返回打乱后的索引值.
tips_10.loc[np.random.permutation(tips_10.index)[:4], 'total_bill'] = np.NaN
tips_10
# 4. 分别计算 Male 和 Female 的平均消费金额, 用于填充对应组的 缺失值.
# 思路1: 直接用 整体的 总消费金额的 平均值 填充.
tips_10.fillna(tips_10.total_bill.mean())

# 思路2: 自定义函数, 计算每组的平均消费金额, 进行填充
def my_mean(col):
    # return col.sum() / col.size     # 某列总金额 / 某列元素个数,  这种写法会导致: 本组所有的数据都会被新值覆盖.
    return col.fillna(col.mean())     # 用该列的平均值, 来填充该列的缺失值, 其它不变.

# 调用上述函数, 实现: 分组填充, 即: 给我N条, 处理后, 还是返回N条数据.
# tips_10.groupby('sex').total_bill.apply(my_mean)      # n => 1  聚合的效果.
tips_10.groupby('sex').total_bill.transform(my_mean)    # n => n  类似于: MySQL的窗口函数的效果.

# df.groupby('sex').total_bill.transform(my_mean)    # n => n  类似于: MySQL的窗口函数的效果.

2.3 分组 + 过滤

python 复制代码
# 1. 查看源数据
df
#%%
# 2. 查看用餐人数情况.
tmp_df = df.groupby('size', as_index=False).total_bill.count()
tmp_df.columns = ['size', 'count']
tmp_df

df.size     # 这样写, 会把 size当做 属性, 而不是 size列.
df['size'].value_counts()
#%%
# 3. 我们发现, 在所有的 消费记录中, 就餐人数 在 1, 5, 6个人的消费次数相对较少, 我们可以过滤掉这部分的数据
tmp_df = df.groupby('size').filter(lambda x : x['size'].count() > 30)
tmp_df
#%%
# 4. 验证上述筛选后的数据, size列只有 2, 3, 4 这三种就餐人数的情况.
tmp_df['size'].value_counts()
#%%
# 5. 上述代码的合并版, 一行搞定.
df.groupby('size').filter(lambda x : x['size'].count() > 30)['size'].value_counts()

# 另外一种筛选的方式, 可以基于: query()函数 + 筛选条件, 找出要的合法的数据. 
df.query('size == 2 or size == 3 or size == 4')
df.query('size in [2, 3, 4]')

2.4 DataFrameGroupby df的分组对象

python 复制代码
# 1. 从小费数据中, 随机的获取10条数据.
tips_10 = pd.read_csv('data/tips.csv').sample(10, random_state=21)
tips_10
#%%
# 2. 演示 根据性别分组, 获取: 分组对象.
grouped = tips_10.groupby('sex')      # DataFrameGroupBy 对象
grouped
#%%
# 3. 遍历上述的分组对象, 看看每个分组都是啥(即: 每个分组的数据)
for sex_group in grouped:
    print(sex_group)        # sex_group: 就是具体的每个分组的数据. 
#%%
# 4. 获取指定的某个分组的数据.
grouped.get_group('Male')
grouped.get_group('Female')
#%%
# 5. 需求: 使用groupby() 按 性别 和 用餐时间分组, 计算小费数据的平均值. 
df.groupby(['sex', 'time']).tip.mean()
#%%
# 6. 分组对象不能使用 0 索引获取数据
grouped
# grouped[0]      # 分组对象不能使用 0 索引获取数据, 要获取数据, 可以通过  grouped.get_group() 函数实现
grouped.get_group(('Male'))

3. 分析案例

python 复制代码
# 1. 加载数据, 获取df对象.
customer_info = pd.read_excel('data/会员信息查询.xlsx')
customer_info.head(100)
#%% md
- 需求1: 按月统计注册的会员数量, 即: 计算 月增量 = 每月新增的会员数
#%%
# 思路1: groupby() + 聚合函数实现. 
# 1. 给原始的df对象, 新增1列, 表示 注册年月. 
# customer_info['注册年月'] =  customer_info['注册时间'].apply(lambda x : x.strftime('%Y-%m')) # %Y: 年,  %m: 月
customer_info.loc[:, '注册年月'] =  customer_info['注册时间'].apply(lambda x : x.strftime('%Y-%m')) # %Y: 年,  %m: 月
customer_info
#%%
# 2. 从原始的df对象中, 获取我们要的字段查看即可.
customer_info[['会员卡号', '会员等级', '会员来源', '注册时间', '注册年月']].head(10)
#%%
# 3. 计算 月增量 = 每个月新增的会员数.
month_count = customer_info.groupby('注册年月')[['会员卡号']].count()
month_count.columns = ['月增量']
month_count

customer_info.注册年月.value_counts().sort_index()       # 效果同上.
#%%
# 思路2: pivot_table() 透视表的方式实现. 
# 参1: index, 索引列, 等价于: groupby()的分组字段.
# 参2: columns, 列索引, 要写的也是 原表中的 列名. 
# 参3: values, 统计字段, 等价于: groupby()的聚合字段.
# 参4: aggfunc, 聚合函数, 默认为: mean
customer_info.pivot_table(index='注册年月', values='会员卡号', aggfunc='count')
#%% md
- 需求2: 按月统计 月存量.  月存量 = 月增量 + 上月存量
#%%
# 1. 计算月存量
month_count['存量'] = month_count['月增量'].cumsum()
# 2. 打印结果.
month_count
#%%
# 3. 可视化数据. 
# 3.1 绘制 月增量, figsize=宽高, color=颜色, secondary_y: 启用双Y轴, legend: 图例, grid: 网格, xlabel=X轴标签, ylabel=Y轴标签
month_count.月增量.plot(figsize=(20, 10), color='green', secondary_y=True, legend=True)

# 3.2 绘制 月存量
month_count.存量.plot(kind='bar', figsize=(20, 10), color='pink', xlabel='年月', ylabel='会员数量', legend=True, grid=True)

#.3 绘制 标题.
plt.title('月增量/存量结果分析', fontsize=21)
相关推荐
Zenova EdgeOS3 分钟前
Python 工业边缘 redis-py 异步实战
开发语言·redis·python
caoerzhong37 分钟前
JeeWMS 开源仓库管理系统权限与域验证解析:Java WMS 如何把数据权限管到仓、货主与人
java·python·开源·vue
一木 之林38 分钟前
Miniconda 安装与 Jupyter 使用入门
ide·python·jupyter
抓不住时间的沙1 小时前
Butterfly主题 5.7 导航栏添加相册同时设置相册入口密码
css·python·node.js
陈老老老板1 小时前
2026企业数据采集选型指南,主流采集 API 与全托管平台深度对比
ai·数据分析
CC数分1 小时前
2026年HRBP岗位硬性要求和加分项
面试·职场和发展·数据分析
风合星语1 小时前
2026 机器人工程实例(五):让 Allegro Hand 在 MuJoCo 中完成接触抓取——抓取状态机、稳定抬升与受控释放
c++·python·机器人·仿真
2601_962680221 小时前
数据分析面试常考的SQL题和业务题分别是哪几类?
sql·面试·数据分析
隐擎fox2 小时前
解构无感人机验证底层机制:行为生物轨迹采样、环境评分模型与自动化对抗实战
自动化测试·python·网络协议·tcp/ip·网络爬虫
haidao0312 小时前
氙灯光源技术特性及其在光催化实验中的标准化应用研究
人工智能·python·能源