美文网首页
深入浅出Pandas--Pandas高级操作--函数应用

深入浅出Pandas--Pandas高级操作--函数应用

作者: 亦是旅人呐 | 来源:发表于2022-11-23 09:11 被阅读0次

    对应书本第二部分第5章Pandas高级操作第7节

    我们知道,函可以让复杂的常用操作模块化,既能在需要使用时直接调用,达到复用的目的,也能简化代码。Pandas提供了几个常用的调用数函数的方法。

    • pipe():应用在整个DataFrame或Series上。
    • apply():应用在DataFrame的行或列中,默认为列。
    • applymap():应用在DataFrame的每个元素中。
    • map():应用在Series或DataFrame的一列的每个元素中。

    pipe()

    Pandas提供的pipe()叫作管道方法,它可以让我们写的分析过程标准化、流水线化,达到复用目标,它也是最近非常流行的链式方法的重要代表。DataFrame和Series都支持pipe()方法。

    pipe()的语法结构为df.pipe(<函数名>, <传给函数的参数列表或字典>)。它将DataFrame或Series作为函数的第一个参数(见图5-1),可以根据需求返回自己定义的任意类型数据。

    pipe()可以将复杂的调用简化,看下面的例子:

    # 对df多重应用多个函数
    f(g(h(df), arg1=a), arg2=b, arg3=c)
    # 用pipe可以把它们连接起来
    (df.pipe(h)
    .pipe(g, arg1=a)
    .pipe(f, arg2=b, arg3=c)
    )
    # 以下是将'arg2'参数传给函数f,然后作为函数整体接受后面的参数
    (df.pipe(h)
    .pipe(g, arg1=a)
    .pipe((f, 'arg2'), arg1=a, arg3=c)
    )
    

    函数h传入df的值返回的结果作为函数g的第一个参数值,g同时还传入了参数arg1;再将返回结果作为函数f的第一个参数,最终得到计算结果,这个调用过程显得异常复杂。使用pipe改造后代码逻辑复杂度大大降低,通过链式调用pipe()方法,对数据进行层层处理,大大提高代码的可读性。

    接下来我们看一下实际案例:

    # 定义一个函数,给所有季度的成绩加n,然后增加平均数
    # 其中n中要加的值为必传参数
    def add_mean(rdf, n):
        df = rdf.copy()
        df = df.loc[:,'Q1':'Q4'].applymap(lambda x: x+n)
        df['avg'] = df.loc[:,'Q1':'Q4'].mean(1)
        return df
    # 调用
    df.pipe(add_mean, 100)
    

    函数部分可以使用lambda。下例完成了一个数据筛选需求,lambda的第一个参数为self,即使用前的数据本身,后面的参数可以在逻辑代码中使用。

    # 筛选出Q1大于等于80且Q2大于等于90的数据
    df.pipe(lambda df_, x, y: df_[(df_.Q1 >= x) & (df_.Q2 >= y)], 80, 90)
    

    apply()

    apply()可以对DataFrame按行和列(默认)进行函数处理,也支持Series。如果是Series,逐个传入具体值,DataFrame逐行或逐列传入,如图5-2所示。

    # 将name全部变为小写
    df.name.apply(lambda x: x.lower())
    

    下面看一个DataFrame的例子。我们需要计算每个季度的平均成绩,计算方法为去掉一个最高分和一个最低分,剩余成绩的平均值为最终的平均分。

    # 去掉一个最高分和一个最低分再算出平均分
    def my_mean(s):
        max_min_ser = pd.Series([-s.max(), -s.min()])
        return s.append(max_min_ser).sum()/(s.count()-2)
    # 对数字列应用函数
    df.select_dtypes(include='number').apply(my_mean)
    

    分析一下代码:函数my_mean接收一个Series,从此Series中取出最大值和最小值的负值组成一个需要减去的负值Series;传入的Series追加此负值Series,最后对Series求和,求和过程中就减去了两个极值;由于去掉了两个值,分母不能取Series的长度,需要减去2,最终计算出结果。这是函数的代码逻辑。

    应用函数时,我们只选择数字类型的列,再使用apply调用函数my_mean,执行后,结果返回了每个季度的平均分。

    希望以此算法计算每个学生的平均成绩,在apply中传入axis=1则每行的数据组成一个Series传入自定义函数中。

    # 同样的算法以学生为维度计算
    (
    df.set_index('name') # 设定name为索引
    .select_dtypes(include='number')
    .apply(my_mean, axis=1) # 横向计算
    )
    

    由上面的案例可见,直接调用lambda函数非常方便。在今后的数据处理中,我们会经常使用这种操作。

    以下是一个判断一列数据是否包含在另一列数据中的案例。

    # 判断一个值是否在另一个类似列表的列中
    df.apply(lambda d: d.s in d.s_list, axis=1) # 布尔序列
    df.apply(lambda d: d.s in d.s_list, axis=1).astype(int) # 0 和 1 序列
    

    它常被用来与NumPy库中的np.where()方法配合使用,如下例:

    # 函数,将大于90分数标记为good
    fun = lambda x: np.where(x.team=='A' and x.Q1>90, 'good' ,'other')
    df.apply(fun, axis=1)
    # 同上效果
    (df.apply(lambda x: x.team=='A' and x.Q1>90, axis=1)
    .map({True:'good', False:'other'})
    )
    df.apply(lambda x: 'good' if x.team=='A' and x.Q1>90 else '', axis=1)
    

    总结一下,apply()可以应用的函数类型如下:

    df.apply(fun) # 自定义
    df.apply(max) # Python内置函数
    df.apply(lambda x: x*2) # lambda
    df.apply(np.mean) # NumPy等其他库的函数
    df.apply(pd.Series.first_valid_index) # Pandas自己的函数
    

    后面介绍到的其他调用函数的方法也适用这个规则。


    applymap()

    df.applymap()可实现元素级函数应用,即对DataFrame中所有的元素(不包含索引)应用函数处理,如图5-3所示。

    使用lambda时,变量是指每一个具体的值

    # 计算数据的长度
    def mylen(x):
        return len(str(x))
    df.applymap(lambda x:mylen(x)) # 应用函数
    df.applymap(mylen) # 效果同上
    

    map()

    map()根据输入对应关系映射值返回最终数据,用于Series对象或DataFrame对象的一列。传入的值可以是一个字典,键为原数据值,值为替换后的值。可以传入一个函数(参数为Series的每个值),还可以传入一个字符格式化表达式来格式化数据内容。

    df.team.map({'A':'一班', 'B':'二班','C':'三班', 'D':'四班',}) # 枚举替换
    df.team.map('I am a {}'.format)
    df.team.map('I am a {}'.format, na_action='ignore')
    t = pd.Series({'six': 6., 'seven': 7.})
    s.map(t)
    # 应用函数
    def f(x):
        return len(str(x))
    df['name'].map(f)
    

    agg()

    agg()一般用于使用指定轴上的一项或多项操作进行汇总,可以传入一个函数或函数的字符,还可以用列表的形式传入多个函数。

    # 每列的最大值
    df.agg('max')
    # 将所有列聚合产生sum和min两行
    df.agg(['sum', 'min'])
    # 序列多个聚合
    df.agg({'Q1' : ['sum', 'min'], 'Q2' : ['min', 'max']})
    # 分组后聚合
    df.groupby('team').agg('max')
    df.Q1.agg(['sum', 'mean'])
    def mymean(x):
        return x.mean()
    df.Q2.agg(['sum', mymean])
    

    另外,agg()还支持传入函数的位置参数和关键字参数,支持每个列分别用不同的方法聚合,支持指定轴的方向。

    # 每列使用不同的方法进行聚合
    df.agg(
    a=('Q1', max),
    b=('Q2', 'min'),
    c=('Q3', np.mean),
    d=('Q4', lambda s:s.sum()+1)
    )
    # 按行聚合
    df.loc[:,'Q1':].agg("mean", axis="columns")
    # 利用pd.Series.add方法对所有数据加分,other是add方法的参数
    df.loc[:,'Q1':].agg(pd.Series.add, other=10)
    

    agg()的用法整体上与apply()极为相似。


    transform()

    DataFrame或Series自身调用函数并返回一个与自身长度相同的数据。

    df.transform(lambda x: x*2) # 应用匿名函数
    df.transform([np.sqrt, np.exp]) # 调用多个函数
    df.transform([np.abs, lambda x: x + 1])
    df.transform({'A': np.abs, 'B': lambda x: x + 1})
    df.transform('abs')
    df.transform(lambda x: x.abs())
    

    可以对比下面两个操作:

    df.groupby('team').sum()
    df.groupby('team').transform(sum)
    

    分组后,直接使用计算函数并按分组显示合计数据。使用transform()调用计算函数,返回的是原数据的结构,但在指定位置上显示聚合计算后的结果,这样方便我们了解数据所在组的情况。


    copy()

    类似于Python中copy()函数,df.copy()方法可以返回一个新对象,这个新对象与原对象没有关系

    当deep = True(默认)时,将创建一个新对象,其中包含调用对象的数据和索引的副本。对副本数据或索引的修改不会反映在原始对象中。当deep = False时,将创建一个新对象而不复制调用对象的数据或索引(仅复制对数据和索引的引用)。原始数据的任何更改都将对浅拷贝的副本进行同步更改,反之亦然。

    s = pd.Series([1, 2], index=["a", "b"])
    s_1 = s
    s_copy = s.copy()
    s_1 is s # True
    s_copy is s # False
    

    熟练使用函数可以帮助我们抽象问题,复用解决方案,同时大大减少代码量。

    相关文章

      网友评论

          本文标题:深入浅出Pandas--Pandas高级操作--函数应用

          本文链接:https://www.haomeiwen.com/subject/ufqftdtx.html