pandas apply() 函数用法

理解 pandas 的函数,要对函数式编程有一定的概念和理解。函数式编程,包括函数式编程思维,当然是一个很复杂的话题,但对今天介绍的 apply() 函数,只需要理解:函数作为一个对象,能作为参数传递给其它参数,并且能作为函数的返回值。

函数作为对象能带来代码风格巨大的改变。举一个例子,有一个包含 1 到 10 的 list,从其中找出能被 3 整除的数字。用传统的方法:

def can_divide_by_three(number):
    if number % 3 == 0:
        return True
    else:
        return False

selected_numbers = []
for number in range(1, 11):
    if can_divide_by_three(number):
        selected_numbers.append(number)

循环是不可少的,因为 can_divide_by_three() 函数只用一次,可以用 lambda 表达式简化:

divide_by_three = lambda x : True if x % 3 == 0 else False

selected_numbers = []
for number in range(1, 11):
    if divide_by_three(item):
        selected_numbers.append(item)

这个就是传统编程思维方式,如果用函数式的编程方法呢,我们可以这样想:从一个 list 中取出特定规则的数字,能不能只关注和设置这个规则,循环这种事情交给编程语言去处理呢?当然可以。当编程人员只关心规则(规则可能是一个条件,或者由某一个 function 来定义),代码将大大简化,可读性也更强。

Python 语言提供 filter() 函数,语法如下:

filter(function, sequence)

filter() 函数的功能:对 sequence 中的 item 依次执行 function(item),将结果为 True 的 item 组成一个 List/String/Tuple(取决于 sequence 的类型)并返回。有了这个函数,上面的代码可以简化为:

divide_by_three = lambda x : True if x % 3 == 0 else False
selected_numbers = filter(divide_by_three, range(1, 11))

将 lambda 表达式放在语句中,代码简化到只需要一句话就够了:

selected_numbers = filter(lambda x: x % 3 == 0, range(1, 11))

Series.apply()

回到主题, pandas 的 apply() 函数可以作用于 Series 或者整个 DataFrame,功能也是自动遍历整个 Series 或者 DataFrame, 对每一个元素运行指定的函数。

举一个例子,现在有这样一组数据,学生的考试成绩:

  Name Nationality  Score
   张           汉    400
   李           回    450
   王           汉    460

如果民族不是汉族,则总分在考试分数上再加 5 分,现在需要用 pandas 来做这种计算,采取增加 pandas 列的方法。当然如果只是为了得到结果, numpy.where() 函数更简单,这里主要为了演示 Series.apply() 函数的用法。

import pandas as pd

df = pd.read_csv("studuent-score.csv")
df['ExtraScore'] = df['Nationality'].apply(lambda x : 5 if x != '汉' else 0)
df['TotalScore'] = df['Score'] + df['ExtraScore']

对于 Nationality 这一列, pandas 遍历每一个值,并且对这个值执行 lambda 匿名函数,将计算结果存储在一个新的 Series 中返回。上面代码在 jupyter notebook 中显示的结果如下:

  Name Nationality  Score  ExtraScore  TotalScore
0    张           汉    400           0         400
1    李           回    450           5         455
2    王           汉    460           0         460

apply() 函数当然也可执行 python 内置的函数,比如我们想得到 Name 这一列字符的个数,如果用 apply() 的话:

df['NameLength'] = df['Name'].apply(len)

DataFrame.apply()

DataFrame.apply() 函数则会遍历每一个元素,对元素运行指定的 function。比如下面的示例:

import pandas as pd
import numpy as np

matrix = [
    [1,2,3],
    [4,5,6],
    [7,8,9]
]

df = pd.DataFrame(matrix, columns=list('xyz'), index=list('abc'))
df.apply(np.square)

对 df 执行 square() 函数后,所有的元素都执行平方运算:

    x   y   z
a   1   4   9
b  16  25  36
c  49  64  81

如果只想 apply() 作用于指定的行和列,可以用行或者列的 name 属性进行限定。比如下面的示例将 x 列进行平方运算:

df.apply(lambda x : np.square(x) if x.name=='x' else x)



    x  y  z
a   1  2  3
b  16  5  6
c  49  8  9

下面的示例对 x 和 y 列进行平方运算:

df.apply(lambda x : np.square(x) if x.name in ['x', 'y'] else x)

    x   y  z
a   1   4  3
b  16  25  6
c  49  64  9

下面的示例对第一行 (a 标签所在行)进行平方运算:

df.apply(lambda x : np.square(x) if x.name == 'a' else x, axis=1)

默认情况下 axis=0 表示按列,axis=1 表示按行。

apply() 计算日期相减示例

平时我们会经常用到日期的计算,比如要计算两个日期的间隔,比如下面的一组关于 wbs 起止日期的数据:

    wbs   date_from     date_to
  job1  2019-04-01  2019-05-01
  job2  2019-04-07  2019-05-17
  job3  2019-05-16  2019-05-31
  job4  2019-05-20  2019-06-11

假定要计算起止日期间隔的天数。比较简单的方法就是两列相减(datetime 类型):

import pandas as pd
import datetime as dt

wbs = {
    "wbs": ["job1", "job2", "job3", "job4"],
    "date_from": ["2019-04-01", "2019-04-07", "2019-05-16","2019-05-20"],
    "date_to": ["2019-05-01", "2019-05-17", "2019-05-31", "2019-06-11"]
}

df = pd.DataFrame(wbs)
df['elpased' = df['date_to'].apply(pd.to_datetime) -   
               df['date_from'].apply(pd.to_datetime)

apply() 函数将 date_fromdate_to 两列转换成 datetime 类型。我们 print 一下 df:

    wbs   date_from     date_to elapsed
0  job1  2019-04-01  2019-05-01 30 days
1  job2  2019-04-07  2019-05-17 40 days
2  job3  2019-05-16  2019-05-31 15 days
3  job4  2019-05-20  2019-06-11 22 days

日期间隔已经计算出来,但后面带有一个单位 days,这是因为两个 datetime 类型相减,得到的数据类型是 timedelta64,如果只要数字,还需要使用 timedeltadays 属性转换一下。

elapsed= df['date_to'].apply(pd.to_datetime) -
    df['date_from'].apply(pd.to_datetime)
df['elapsed'] = elapsed.apply(lambda x : x.days)

使用 DataFrame.apply() 函数也能达到同样的效果,我们需要先定义一个函数 get_interval_days() 这个函数的第一列是一个 Series 类型的变量,执行的时候,依次接收 DataFrame 的每一行。

import pandas as pd
import datetime as dt

def get_interval_days(arrLike, start, end):   
    start_date = dt.datetime.strptime(arrLike[start], '%Y-%m-%d')
    end_date = dt.datetime.strptime(arrLike[end], '%Y-%m-%d') 

    return (end_date - start_date).days


wbs = {
    "wbs": ["job1", "job2", "job3", "job4"],
    "date_from": ["2019-04-01", "2019-04-07", "2019-05-16","2019-05-20"],
    "date_to": ["2019-05-01", "2019-05-17", "2019-05-31", "2019-06-11"]
}

df = pd.DataFrame(wbs)
df['elapsed'] = df.apply(
    get_interval_days, axis=1, args=('date_from', 'date_to'))

工作中应用apply()函数1

    def all_mutation_no_same_phgvs(self, wildtype=False):
        '''所有突变点,不包括野生型突变 phgvs为空
        '''

        def class_type(mtype):
            if mtype == 'snv':
                return '点突变\n插入缺失变异'
            elif mtype == 'cnv':
                return '拷贝数变异'
            elif mtype == 'fusion':
                return '融合变异'
            elif mtype == 'skipping':
                return 'Skipping'
            else:
                return 'Unknown'

        df = self._mutations(wildtype=wildtype)
        df = self._change_special_mutationtype(
            df, mapping_phgvs=True, replace_types=True, replace_wild=True)
        df['phgvs'] = df.apply(lambda x: '' if x['mtype']
                                               != 'snv' else x['phgvs'], axis=1)
        df['mtype'] = df.apply(lambda x: class_type(x['mtype']), axis=1)
        return df

    def approved_clinical_meaning(self):
        '''具有临床意义的变异 (不含野生型)'''

        df = pd.concat([
            self.group_data['current_tumor_sensitive'],
            self.group_data['current_tumor_maybe_sensitive'],
            self.group_data['current_tumor_negative'],
            self.group_data['current_tumor_maybe_negative'],
            self.group_data['current_tumor_less_effective'],
            self.group_data['other_tumor_sensitive'],
            self.group_data['other_tumor_maybe_sensitive'],
            self.group_data['other_tumor_negative'],
            self.group_data['other_tumor_maybe_negative'],
            self.group_data['other_tumor_less_effective'],
        ])

        df = df[['genesymbol', 'exonintron', 'ghgvs', 'chgvs',
                 'phgvs', 'vaf', 'mtype', 'somger']].drop_duplicates()
        df = df[df['phgvs'] != '野生型']
        df['genesymbol'] = df.apply(
            lambda x: '' if x['mtype'] == '融合变异' else x['genesymbol'], axis=1)
        df['phgvs'] = df.apply(
            lambda x: '' if x['mtype'] == '拷贝数变异' else x['phgvs'], axis=1)
        df['phgvs'] = df.apply(
            lambda x: '' if x['mtype'] == '融合变异' else x['phgvs'], axis=1)
        df['phgvs'] = df.apply(
            lambda x: '' if x['mtype'] == 'Skipping' else x['phgvs'], axis=1)
        return df.sort_values(by=['genesymbol'], ascending=True).copy()

工作中应用apply()函数2

HETHOM_DICT = {
    'Het': '杂合',
    'Hom': '纯合',
    'na': '--'
}
df['vaf'] = df.apply(lambda r: HETHOM_DICT.get(r['hethom']) if r['somger'] == 'GER' else r['vaf'], axis=1)


这段代码是用来将数据框 df 中的 vaf 列中的值转换为字符串。如果 somger 列的值为 GER,则将 vaf 列的值转换为 HETHOM_DICT 中的对应值,否则将 vaf 列的值保持不变。

HETHOM_DICT 是一个字典,其中 key 是 vaf 列中的值,value 是 vaf 列中的值对应的字符串。

df.apply() 函数用于对数据框中的每一行应用一个函数。在本例中,该函数是 lambda r: HETHOM_DICT.get(r['hethom']) if r['somger'] == 'GER' else r['vaf']。

该函数首先检查 somger 列的值是否为 GER。如果是,则将 vaf 列的值转换为 HETHOM_DICT 中的对应值。否则,将 vaf 列的值保持不变。

axis=1 参数指定了 apply() 函数在哪个轴上应用函数。在本例中,axis=1 表示在行上应用函数。

 参考

pandas apply() 函数用法 - 简书 (jianshu.com)

Pandas的Apply函数——Pandas中最好用的函数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ゆきな

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值