• Pandas知识点-详解行列级批处理函数apply


    Pandas知识点-详解行列级批处理函数apply

    在Pandas中,DataFrame和Series等对象需要执行批量处理操作时,可以借用apply()函数来实现。
    apply()的核心功能是实现“批量”调度处理,至于批量做什么,由用户传入的函数决定(自定义或现成的函数)。函数传递给apply(),apply()会帮用户在DataFrame和Series等对象中(按行或按列)批量执行传入的函数。
    先看一个例子:

    # coding=utf-8
    import pandas as pd
    
    df = pd.DataFrame({'Col-1': [1, 3, 5], 'Col-2': [2, 4, 6], 'Col-3': [9, 8, 7], 'Col-4': [3, 6, 9]},
                      index=['A', 'B', 'C'])
    print(df)
    df_new = df.apply(lambda x: x-1)
    print('-' * 30, '\n', df_new, sep='')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
       Col-1  Col-2  Col-3  Col-4
    A      1      2      9      3
    B      3      4      8      6
    C      5      6      7      9
    ------------------------------
       Col-1  Col-2  Col-3  Col-4
    A      0      1      8      2
    B      2      3      7      5
    C      4      5      6      8
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    从这个例子可以看出,apply的使用非常简便且优雅,一行代码就对DataFrame中的所有数据都执行了一遍传入的匿名函数。
    apply用法和参数介绍

    apply(self, func, axis=0, raw=False, result_type=None, args=(), **kwds):
    func: 应用于每一列或每一行的函数,这个函数可以是Python内置函数、Pandas或其他库中的函数、自定义函数、匿名函数。
    axis: 设置批处理函数按列还是按行应用,0或index表示按列应用函数,1或columns表示按行应用函数,默认值为0。
    raw: 设置将列/行作为Series对象传递给函数,还是作为ndarray对象传递给函数。raw是bool类型,默认为False。
    	False: 将列/行作为Series对象传递给函数。
    	True: 将列/行作为ndarray对象传递给函数。apply中的func函数将接收ndarray对象,如果应用numpy中的函数,这样可以提升性能。
    result_type: 当axis=1时,设置返回结果的类型和样式,支持{'expand', 'reduce', 'broadcast', None}四种类型,默认为None。
    	expand: 列表式的结果将被转化为列。
    	reduce: 如果可能的话,返回一个Series,而不是返回列表式的结果。这与expand相反。
    	broadcast: 结果将被广播成DataFrame的原始形状,DataFrame的原始索引和列将被保留。
    	None: 结果取决于应用函数的返回值,列表式的结果将以Series形式返回,如果应用函数返回Series将会扩展到列。
    args: 传给应用函数func的位置参数,args接收的数据类型为元组,如果只有一个位置参数要注意加逗号。
    **kwds: 如果func中有关键字参数,可以传给**kwds。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    raw和result_type通常不需要自己设置,保持默认即可。
    下面依次介绍apply()函数的各种用法。
    传入不同类型的函数

    import numpy as np
    
    df = pd.DataFrame({'Col-1': [1, 3, 5], 'Col-2': [2, 4, 6], 'Col-3': [9, 8, 7], 'Col-4': [3, 6, 9]},
                      index=['A', 'B', 'C'])
    print(df)
    df1 = df.apply(max)  # python内置函数
    print('-' * 30, '\n', df1, sep='')
    df2 = df.apply(np.mean)  # numpy中的函数
    print('-' * 30, '\n', df2, sep='')
    df3 = df.apply(pd.DataFrame.min)  # pandas中的方法
    print('-' * 30, '\n', df3, sep='')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
       Col-1  Col-2  Col-3  Col-4
    A      1      2      9      3
    B      3      4      8      6
    C      5      6      7      9
    ------------------------------
    Col-1    5
    Col-2    6
    Col-3    9
    Col-4    9
    dtype: int64
    ------------------------------
    Col-1    3.0
    Col-2    4.0
    Col-3    8.0
    Col-4    6.0
    dtype: float64
    ------------------------------
    Col-1    1
    Col-2    2
    Col-3    7
    Col-4    3
    dtype: int64
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    def make_ok(s):
        return pd.Series(['{}ok'.format(d) for d in s])
    
    
    df4 = df.apply(make_ok)  # 自定义函数
    print('-' * 30, '\n', df4, sep='')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    ------------------------------
      Col-1 Col-2 Col-3 Col-4
    0   1ok   2ok   9ok   3ok
    1   3ok   4ok   8ok   6ok
    2   5ok   6ok   7ok   9ok
    
    • 1
    • 2
    • 3
    • 4
    • 5

    设置按行还是按列

    def make_ok(s):
        if isinstance(s, pd.Series):
            if s.name in df.columns:
                return pd.Series(['{}ok-列'.format(d) for d in s])
            else:
                return pd.Series(['{}ok-行'.format(d) for d in s])
        else:
            return '{}ok'.format(s)
    
    
    df5 = df.apply(make_ok, axis=0)  # 按列处理
    print('-' * 30, '\n', df5, sep='')
    df6 = df.apply(make_ok, axis=1)  # 按行处理
    print('-' * 30, '\n', df6, sep='')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    ------------------------------
       Col-1  Col-2  Col-3  Col-4
    0  1ok-列  2ok-列  9ok-列  3ok-列
    1  3ok-列  4ok-列  8ok-列  6ok-列
    2  5ok-列  6ok-列  7ok-列  9ok-列
    ------------------------------
           0      1      2      3
    A  1ok-行  2ok-行  9ok-行  3ok-行
    B  3ok-行  4ok-行  8ok-行  6ok-行
    C  5ok-行  6ok-行  7ok-行  9ok-行
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    这里推演一下按列和按行的过程,当axis参数为0或index时,按列从DataFrame中取数据,如上面的例子先取到第一列Col-1:[1, 3, 5],将第一列传给函数func,然后取第二列Col-2:[2, 4, 6]…以此类推。当axis参数为1或columns时,按行从DataFrame中取数据,如上面的例子先取到第一行A:[1, 2, 9, 3],将第一行传递给函数func执行后取第二行,以此类推。
    同时,当按列应用函数时,DataFrame的index变成了默认索引(0开始的自然数),当按行应用函数时,DataFrame的columns变成了默认索引,如果要保持与原DataFrame一样,则需要重新设置。
    函数func的参数

    def yes_or_no(s, answer):
        if answer != 'yes' and answer != 'no':
            answer = 'yes'
        if isinstance(s, pd.Series):
            return pd.Series(['{}-{}'.format(d, answer) for d in s])
        else:
            return '{}-{}'.format(s, answer)
    
    
    df7 = df.apply(yes_or_no, args=('yes',))
    df7.index = ['A', 'B', 'C']
    print('-' * 30, '\n', df7, sep='')
    df8 = df.apply(yes_or_no, args=('no',))
    print('-' * 30, '\n', df8, sep='')
    df9 = df.apply(yes_or_no, args=(0,))
    print('-' * 30, '\n', df9, sep='')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    ------------------------------
       Col-1  Col-2  Col-3  Col-4
    A  1-yes  2-yes  9-yes  3-yes
    B  3-yes  4-yes  8-yes  6-yes
    C  5-yes  6-yes  7-yes  9-yes
    ------------------------------
      Col-1 Col-2 Col-3 Col-4
    0  1-no  2-no  9-no  3-no
    1  3-no  4-no  8-no  6-no
    2  5-no  6-no  7-no  9-no
    ------------------------------
       Col-1  Col-2  Col-3  Col-4
    0  1-yes  2-yes  9-yes  3-yes
    1  3-yes  4-yes  8-yes  6-yes
    2  5-yes  6-yes  7-yes  9-yes
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在apply()中,func函数的第一个参数默认会传入Series对象,这就是前面说的“将列/行作为Series对象传递给函数”,因此函数func至少要有一个参数,这个参数相当于类方法中的self,不需要在args中传值。如果func没有参数,则不能在apply中使用。
    如果func的参数多于一个,则多出来的参数通过args传递,args接收一个元组,args里只有一个值时,需要加上逗号。如果func中有关键字参数,可以传到apply中**kwds的位置。
    传入多个函数进行聚合

    df10 = df.apply([np.max, np.min])
    print('-' * 40, '\n', df10, sep='')
    df11 = df.apply({'Col-1': np.mean, 'Col-2': np.min})
    print('-' * 40, '\n', df11, sep='')
    df12 = df.apply({'Col-1': [np.mean, np.median], 'Col-2': [np.min, np.mean]})
    print('-' * 40, '\n', df12, sep='')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    ----------------------------------------
          Col-1  Col-2  Col-3  Col-4
    amax      5      6      9      9
    amin      1      2      7      3
    ----------------------------------------
    Col-1    3.0
    Col-2    2.0
    dtype: float64
    ----------------------------------------
            Col-1  Col-2
    mean      3.0    4.0
    median    3.0    NaN
    amin      NaN    2.0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    当在apply中传入多个函数时,返回的结果被聚合成一个新的DataFrame或Series,作用类似于pandas中的聚合函数DataFrame.agg()。[后续文章会介绍agg()]
    通过函数名字符串调用函数

    df13 = df.apply('mean', axis=1)
    print('-' * 30, '\n', df13, sep='')
    df14 = df.apply(['mean', 'min'], axis=1)
    print('-' * 30, '\n', df14, sep='')
    
    • 1
    • 2
    • 3
    • 4
    ------------------------------
    A    3.75
    B    5.25
    C    6.75
    dtype: float64
    ------------------------------
       mean  min
    A  3.75  1.0
    B  5.25  3.0
    C  6.75  5.0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    apply()支持函数名用字符串传给func,调用函数。
    修改DataFrame本身

    df15 = df.copy()
    # 读取df的一列,将处理结果添加到原df中,增加一列
    df15['Col-x'] = df15['Col-1'].apply(make_ok)
    print('-' * 40, '\n', df15, sep='')
    # 读取df的一行,将处理结果添加到原df中,增加一行
    df15.loc['Z'] = df15.loc['A'].apply(yes_or_no, args=('yes',))
    print('-' * 40, '\n', df15, sep='')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    ----------------------------------------
       Col-1  Col-2  Col-3  Col-4 Col-x
    A      1      2      9      3   1ok
    B      3      4      8      6   3ok
    C      5      6      7      9   5ok
    ----------------------------------------
       Col-1  Col-2  Col-3  Col-4    Col-x
    A      1      2      9      3      1ok
    B      3      4      8      6      3ok
    C      5      6      7      9      5ok
    Z  1-yes  2-yes  9-yes  3-yes  1ok-yes
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    DataFrame增加一列或一行可以直接赋值,修改一个DataFrame后将结果赋值给本身,这样相当于修改了原始DataFrame。
    Series使用apply

    s0 = df['Col-2'].apply(make_ok)
    print('-' * 20, '\n', s0, sep='')
    s = pd.Series(range(5), index=[alpha for alpha in 'abcde'])
    print('-' * 20, '\n', s, sep='')
    s1 = s.apply(make_ok)
    print('-' * 20, '\n', s1, sep='')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    --------------------
    A    2ok
    B    4ok
    C    6ok
    Name: Col-2, dtype: object
    --------------------
    a    0
    b    1
    c    2
    d    3
    e    4
    dtype: int64
    --------------------
    a    0ok
    b    1ok
    c    2ok
    d    3ok
    e    4ok
    dtype: object
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    DataFrame中的一行或一列都是一个Series,所以用DataFrame的列或行调用apply()就相当于Series调用apply()。
    在DataFrame中,apply()将行/列作为Series传给func函数,在Series中,apply()将Series中的每一个值传给func函数。对于这两种情况,func接受的参数类型完全不一样,因此使用时一定要注意func函数的参数类型,否则可能不适用。

    s2 = s.apply(np.mean)
    print('-' * 20, '\n', s2, sep='')
    s3 = np.mean(s)
    print('-' * 20, '\n', s3, sep='')
    
    • 1
    • 2
    • 3
    • 4
    --------------------
    a    0.0
    b    1.0
    c    2.0
    d    3.0
    e    4.0
    dtype: float64
    --------------------
    2.0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    将Series中的每一个值传给apply()中的函数func,返回的结果仍然是一个Series。将Series作为一个整体传给apply()中的函数func,有些函数返回的结果仍然是Series,如上面的自定义函数,有些函数返回的结果不再是Series,而是一个其他类型的数据,如numpy中的统计运算函数(mean、max、min)等。
    因此DataFrame经过apply()批处理后,可能会变成一个Series,这是由apply()中的函数func的返回值决定的,与apply()无关。
    以上就是pandas中的apply()函数的用法介绍和分析,希望对你有帮助,想要深入的了解apply()函数的底层原理,可以打个断点,在“Debugger”模式中看运行过程,也可以看源码。如果你有其他的想法或疑问,欢迎加我好友一起交流讨论。

    参考文档:
    [1] pandas中文网:https://www.pypandas.cn/docs/

  • 相关阅读:
    人工智能:塑造未来生活的强大力量
    CentOS7如何设置开机自启动程序、开机自启动脚本?
    RabbitMq-监控管理
    服务器如何防御攻击,有哪些方法
    投屏工具AirDroid Cast竟然成为手机拍照神助攻?快来看看怎么操作
    工程化:Loader 介绍
    linux(1.nginx基础 && 2.使用Nginx负载均衡及动静分离)
    如何做好漏洞扫描工作提高网络安全
    图深度学习_简介
    Vue3项目创建+组合式API使用+组件通信+渲染微博热搜+打包上线
  • 原文地址:https://blog.csdn.net/weixin_43790276/article/details/125532026