数据处理与统计分析篇-day08-apply()自定义函数与分组操作

news2024/9/23 3:36:48

一. 自定义函数

概述

  1. 当Pandas自带的API不能满足需求, 例如: 我们需要遍历的对Series中的每一条数据/DataFrame中的一列或一行数据做相同的自定义处理, 就可以使用Apply自定义函数

  2. apply函数可以接收一个自定义函数, 可以将Series对象的逐个值或DataFrame的行/列数据传递给自定义函数处理

  3. apply函数类似于编写一个for循环, 遍历行/列的每一个元素,但比使用for循环效率高很多

导包:

import numpy as np
import pandas as pd
import os
​
os.chdir(r'D:\CodeProject\03data_processing_analysis\teacher_project')  # 改变当前的工作目录.  change current work directory

apply()操作Series对象

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

# 1. 定义1个df对象.
df = pd.DataFrame({'a': [10, 20, 30], 'b': [20, 30, 40]})
df
​
# 2. 定义1个函数, 用于求 值 的 平方.  2 => 4,   5 => 25
def my_fun1(x):
    print('看看我执行了嘛!')
    return x ** 2
​
​
# 扩展: 定义函数, 计算x的e次方.
def my_fun2(x, e):
    return x ** e
​
# 3. 把上述的函数, 作用于 df对象的 a列值(Series对象)
df['a'].apply(my_fun1)  # 细节: 这里写的是函数名, 即: 函数对象.  如果写: 函数名() 则表示是在调用函数.
​
df.a.apply(my_fun2, e=3)  # 细节: 传参数时, 使用 关键字参数 写法进行传参.

apply()操作DF对象

df的apply(func, axis=)函数, 默认是传入整列值的, 而不是逐个值进行传入的.

源码解释axis参数:0 or index: apply function to each colum1 or columns: apply function to each row

解释:

axis = 0 按列传递数据 传入一列数据(Series)

axis = 1 按行传递数据 传入一列数据(Series)

df
df.apply(func1)     # 默认axis=0, 代表列, 即: 整列值传入
​
# 计算平均值,验证默认是传入整列值
def func3(x, y, z):
    return (x + y + z) / 3
​
# 报错,apply函数默认将df对象的整列作为参数传入
df.apply(func3)
​
def func4(x):
    print(x)
    print(type(x))
    
df.apply(func4)
​
def func5(x):
    return x.mean()
​
df.apply(func5)             # 默认: axis=0(列)
df.apply(func5, axis=0)     # 效果同上
df.apply(func5, axis=1)     # 行 传入

函数向量化

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

在处理向量和标量时, 无法将向量直接和标量进行比较, 虽然手动用for循环遍历不会报错, 但是结果不对.

此时需要使用np.vectorize()函数, 将自定义函数向量化. 即: 如果遇到了向量, 则会逐个进行遍历, 获取标量并操作.

函数向量化的写法类似于装饰器的写法

# 定义函数, 接收df对象的两列数据, 计算每行的平均值@np.vectorize
@np.vectorize
def func6(x, y):
    # 判断, 如果x的值是20, 就返回NaN
    if x == 2:
        return np.NAN
    
    # x 第一列, y 第二列
    return (x + y) / 2
​
func6(df.a, df.b)
​
# 使用np.vectorize()函数, 将自定义函数进行向量化
func6 = np.vectorize(func6)
func6(df.a, df.b)

apply()结合lambda表达式

如果需求比较简单, 没有必要重新定义1个新的函数, 可以直接传入Lambda表达式.

# 1. 定义数据集.
df = pd.DataFrame({'a': [10, 20, 30], 'b': [20, 30, 40]})
df
#%%
# 2. 需求: 每个值 => 该值的平方.
def my_fun1(x):
    return x ** 2
​
df.apply(my_fun1)
#%%
# 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)   # 统计每行的平均值

apply()函数案例

加载数据

# 1. 加载数据集, 获取df对象.
train = pd.read_csv('data/titanic_train.csv')
train.head()
#%%
# 2. 查看数据集的 常用统计值.
train.info()
train.describe()
train
train.shape         # (891, 12)
len(train)          # 891 行数
train.size          # 891 * 12 = 10,772
​
len(train.Age)      # 891
train.Age.size      # 891

需求1:计算每列null总数, 缺失值占比, 非缺失值占比

# 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: 计算泰坦尼克号数据中, 各年龄段总人数

# 方式1: 直接算每个年龄出现了多少次, 即: 每个年龄的总人数, 但是达不到我们要的效果.
train.Age.value_counts()
​
# 方式2:解题思路: 把年龄变成年龄段的值, 然后再进行统计.
# 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的客户总数

# VIP规则, 乘客船舱等级为1, 或者 名字中带有: 'Master', 'Sir', 'Dr'
def is_vip(rows):
    if rows.Pclass == 1 and ('Master' in rows.Name or 'Sir' in rows.Name or 'Dr' in rows.Name):
        return 'vip'
    else:
        return 'not_vip'
​
train.apply(is_vip, axis=1).value_counts()

二. 分组操作

分组 + 聚合

概述

  1. 在SQL中我们经常使用 GROUP BY 将某个字段,按不同的取值进行分组,

  2. 在pandas中也有groupby函数, 分组之后,每组都会有至少1条数据, 将这些数据进一步处理,

  3. 返回单个值的过程就是聚合,比如分组之后计算算术平均值, 或者分组之后计算频数,都属于聚

代码演示

导入数据
# 1. 读取数据, 获取df对象
df = pd.read_csv('data/gapminder.tsv', sep='\t')
df.head()
单变量
# 统计每年平均寿命
# 写法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()
​
# 写法2
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')  # 效果同上.
多变量agg
# 需求: 统计各个大洲 平均寿命, 人口的中位数, 最大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()
自定义函数聚合运算
# 需求: 计算各个大洲的平均寿命
# 方式1: 使用Pandas的mean()函数.
df.groupby('continent').lifeExp.mean()
df.groupby('continent').lifeExp.agg('mean')
​
# 方式2: 使用自定义函数, 计算平均值.
# 1. 定义函数, 计算某列的平均值.
def my_mean(col):
    # 某列平均值 = 该列元素和 / 该列元素个数
    # return col.sum() / len(col)
    return col.sum() / col.size
​
​
# 2. 调用函数.
df.groupby('continent').lifeExp.apply(my_mean)
df.groupby('continent').lifeExp.agg(my_mean)

分组 + 转换

概述

  1. transform 需要把DataFrame中的值传递给一个函数, 而后由该函数"转换"数据。

  2. 即: aggregate(聚合) 返回单个聚合值,但transform 不会减少数据量。

  3. 分组转换跟SQL中的窗口函数中的聚合函数作用一样。可以把每一条数据和这个数据所属的组的一个聚合值在放在一起, 可以根据需求进行相应计算。

代码演示

计算x的z-score分数

计算x的 z-score分数, 也叫: 标准分数, 公式为: (x - x_mean) / x_std

# 1. 查看数据源
df
#%%
# 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()解释: 随机打乱索引值, 并返回打乱后的索引值.
# 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的窗口函数的效果.

分组 + 过滤

概述

  1. 使用groupby方法还可以过滤数据

  2. 调用filter 方法,传入一个返回布尔值的函数,返回False的数据会被过滤掉

代码演示

# 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]')

DataFrameGroupby对象

概述

调用了groupby方法之后, 就会返回一个DataFrameGroupby对象

代码演示

# 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'))

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2156453.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Object类代码结构

Object Object是所有类的父类。 方法结构如下 一些不知道的方法 private static native void registerNatives(); * JNI机制 * 这里定义了一个 native 方法 registerNatives()&#xff0c;它没有方法体。 * native 关键字表示这个方法的实现是由本地代码 * &#xff08;通常…

传输层 IV(TCP协议——流量控制、拥塞控制)【★★★★】

&#xff08;★★&#xff09;代表非常重要的知识点&#xff0c;&#xff08;★&#xff09;代表重要的知识点。 一、TCP 流量控制&#xff08;★★&#xff09; 1. 利用滑动窗口实现流量控制 一般说来&#xff0c;我们总是希望数据传输得更快一些。但如果发送方把数据发送得…

java基础知识20 Intern方法的作用

一 Intern方法作用 1.1 Intern方法 1.在jdk1.6中&#xff1a; intern()方法&#xff1a;在jdk1.6中&#xff0c;根据字符串对象&#xff0c;检查常量池中是否存在相同字符串对象 如果字符串常量池里面已经包含了等于字符串X的字符串&#xff0c;那么就返回常量池中这个字符…

基于高维多目标优化的无人机三维航迹规划,MATLAB代码

高维多目标优化问题是指目标数量大于3的优化问题&#xff0c;这类问题在实际应用中非常普遍&#xff0c;如工业生产、资源管理、工程设计等领域。随着目标数量的增加&#xff0c;问题的求解难度也随之增大&#xff0c;传统的多目标优化算法在处理高维多目标问题时面临着选择压力…

window系统DockerDesktop 部署windows容器

目录 参考文献1、安装Docker Desktop1.1 下载安装包1.2 安装教程1.3 异常解决 2、安装windows容器2.1 先启动DockerDesktop 软件界面2.2 检查docker版本2.3 拉取windows镜像 参考文献 windows容器docker中文官网 Docker: windows下跑windows镜像 1、安装Docker Desktop 1.1 …

Llama3.1的部署与使用

✨ Blog’s 主页: 白乐天_ξ( ✿&#xff1e;◡❛) &#x1f308; 个人Motto&#xff1a;他强任他强&#xff0c;清风拂山冈&#xff01; &#x1f4ab; 欢迎来到我的学习笔记&#xff01; 什么是Llama3.1&#xff1f; Llama3.1 是 Meta&#xff08;原 Facebook&#xff09;公…

java项目之线上辅导班系统的开发与设计

项目简介 基于springboot的线上辅导班系统的开发与设计的主要使用者分为&#xff1a; 管理员在后台主要管理字典管理、论坛管理、公开课管理、课程管理、课程报名管理、课程收藏管理、课程留言管理、师资力量管理、用户管理、管理员管理等。 &#x1f495;&#x1f495;作者&a…

二分查找算法(2) _在排序数组中查找元素的第一个和最后一个_模板

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 二分查找算法(2) _在排序数组中查找元素的第一个和最后一个_模板 收录于专栏【经典算法练习】 本专栏旨在分享学习算法的一点学习笔记&#xff0c;欢迎大家在评…

算法-K个一组翻转链表

// 要实现没k个节点进行翻转的操作&#xff0c;可以按照一下步骤进行 // 1.计算链表长度 // 2.分组反转 // 3. 使用一个虚拟头节点来处理边界情况 // 4.每次处理k个节点进行反转 // 5.如果剩余节点不足k个 则保持原有顺序 // 6.依次反转每组中的节点 // 1.使用prevGroupEEnd追…

EvilScience靶机详解

主机发现 arp-scan -l 得到靶机ip 192.168.229.152 端口扫描 nmap -sV -A -T4 192.168.1.20 这段代码使用 nmap 命令来扫描目标主机 192.168.1.20&#xff0c;并执行以下操作&#xff1a;-sV&#xff1a;探测开放的端口&#xff0c;以确定服务/版本信息。-A&#xff1a;启…

[大语言模型] LINFUSION:1个GPU,1分钟,16K图像

1. 文章 2409.02097 (arxiv.org)https://arxiv.org/pdf/2409.02097 LINFUSION: 1 GPU, 1 MINUTE, 16K IMAGE 摘要 本文介绍了一种新型的扩散模型LINFUSION&#xff0c;它能够在保持高分辨率图像生成性能的同时显著降低时间和内存复杂度。该模型采用了基于Transformer的UNet进…

常用卫星学习

文章目录 Landsat-8 Landsat-8 由一台操作陆地成像仪 &#xff08;OLI&#xff09; 和一台热红外传感器 &#xff08;TIRS&#xff09;的卫星&#xff0c;OLI 提供 9 个波段&#xff0c;覆盖 0.43–2.29 μm 的波长&#xff0c;其中全色波段&#xff08;一般指0.5μm到0.75μm左…

Java的IO流(二)

目录 Java的IO流&#xff08;二&#xff09; 字节缓冲流 基本使用 使用缓冲流复制文件 字符缓冲流 缓冲流读取数据原理 字符编码 字符集 转换流 序列化流与反序列化流 基本使用 禁止成员被序列化 序列号不匹配异常 打印流 基本使用 系统打印流与改变流向 Prop…

【kaggle竞赛】毒蘑菇的二元预测题目相关信息和思路求解代码

毒蘑菇的二元预测 您提供了很多关于不同二元分类任务的资源和链接&#xff0c;看起来这些都是Kaggle竞赛中的参考资料和高分解决方案。为了帮助您更好地利用这些资源&#xff0c;这里是一些关键点的总结&#xff1a; Playground Season 4 Episode 8 主要关注的竞赛: 使用银行…

2024 硬盘格式恢复软件大揭秘

宝妈们硬盘存储图片、设计师用硬盘存储素材、学生们用硬盘存储作业和数据已经是一个普遍的社会现象了。但是有时候数据迁移之后想要一份全新的硬盘我们就会采取硬盘格式化的操作&#xff0c;如果格式化之后发现硬盘数据没有备份好硬盘格式化后能恢复数据吗&#xff1f;这次我就…

没错,我给androidx修了一个bug!

不容易啊&#xff0c;必须先截图留恋&#x1f601; 这个bug是发生在xml中给AppcompatTextView设置textFontWeight&#xff0c;但是却无法生效。修复bug的代码也很简单&#xff0c;总共就几行代码&#xff0c;但是在找引起这个bug的原因和后面给androidx提pr却花了很久。 //App…

git学习【完结】

git学习【完结】 文章目录 git学习【完结】一、Git基本操作1.创建本地仓库2.配置本地仓库1.局部配置2.全局配置 3.认识工作区、暂存区、版本库4.添加文件5.修改文件6.版本回退7.撤销修改8.删除文件 二、Git分支管理1.理解分支2.创建、切换、合并分支3.删除分支4.合并冲突5.合并…

【每天学个新注解】Day 2 Lombok注解简解(一)—@Data、@Build、@Value

Data 相当于同时使用了 Getter 、Setter 、RequiredArgsConstructor、ToString、EqualsAndHashCode 1、如何使用 需要同时使用Getter 、Setter 、RequiredArgsConstructor、ToString、EqualsAndHashCode注解一个Bean的时候。 2、代码示例 例&#xff1a; Data public cla…

H5白色大方图形ui设计公司网站HTML模板源码

源码名称&#xff1a;白色大方图形ui设计公司网站模板源码 源码介绍&#xff1a;一款H5自适应白色大方图形ui设计公司官网网站模板源码。源码含有七个页面&#xff0c;可用于各种设计公司官网。 需求环境&#xff1a;H5 下载地址&#xff1a; https://www.51888w.com/369.ht…

基于vue框架的宠物托管系统设计与实现is203(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;用户,宠物种类,商家,咨询商家,用户宠物,宠物托管,宠物状况,宠物用品,用品分类,商家公告,结束托管,账单信息,延长托管 开题报告内容 基于Vue框架的宠物托管系统设计与实现开题报告 一、引言 随着现代生活节奏的加快&#xff0c;越来越…