一、函数
对函数的初步了解见:
06_函数基础与库的使用
二、使用函数
在知道了函数的定义和使用方法之后,下面进一步介绍在Pandas中如何使用函数。
# 导入包import pandas as pd# 创建一个简单的包含两列的DataFramedf = pd.DataFrame({'a': [10, 20, 30],'b': [20, 30, 40]})print(df)
2.1. Series的apply方法
在Pandas中,当从一个DataFrame取其中一列或一行时,返回的对象类型时Series。
print(type(df['a']))# <class 'pandas.core.series.Series'>print(type(df.iloc[0, :]))# <class 'pandas.core.series.Series'>
Series有一个apply方法,可以传入一个函数(func),传入之后apply方法会把传入的函数应用于Series的每个元素。<br />例如前面定义的计算平方函数:
def my_square(x):"""求平方"""return x ** 2sq = df['a'].apply(my_square)print(sq)# 查看返回结果的类型print(type(sq))
需要注意的时,当把my_square传递给apply时,不需要在my_square后面加圆括号。在my_square函数中,指数被固定为2(即求平方),如果我们把指数变成一个参数,就可以求任意一个数的任意次幂:
def my_exp(x, e):return x ** e# 调用该函数时,需要输入两个参数my_exp(2, 3)# my_exp应用于Series的apply方法:ex = df['a'].apply(my_exp, e=3)print(ex)
当传递给apply的是一个有多个参数的函数时,需要将其余参数一并传入,并且参数的名称不能省略。
2.2. DataFrame的apply方法
DataFrame也有apply方法,但是其包含两个维度(行和列),因此在使用时需要指定是逐行计算还是逐列计算:
def my_print(x):print(x)# 通过axis参数指定,默认axis=0,表示逐列计算,axis=1表示逐行计算df.apply(my_print)df.apply(my_print, axis=0)df.apply(my_print, axis=1)
当向DataFrame的apply方法传递一个函数时,整个轴(行或列)会作为函数的第一个参数传递到函数中。
def avg_3(x, y, z):return (x + y + z) / 3# 直接调用会报错print(df.apply(avg_3))# 提示没有输入参数y和zdef avg_3_apply(row):x = row[0]y = row[1]z = row[2]return (x + y + z) / 3print(df.apply(avg_3_apply))
2.3. apply的应用
前面的介绍中,创建了一个3行2列的小数据集讲解apply方法的工作原理。下面将使用一个更实际的数据集详细介绍apply方法的应用。
本例中数据集来自seaborn库内置的tiantic数据集,数据记录了乘客在沉船事故中是否幸存下来。
import seaborn as snstitanic = sns.load_dataset("titanic")# 该数据集是一个DataFrameprint(type(titanic))# 查看该数据集的基本特征print(titanic.info())# RangeIndex: 891 entries, 0 to 890# Data columns (total 15 columns):# # Column Non-Null Count Dtype# --- ------ -------------- -----# 0 survived 891 non-null int64# 1 pclass 891 non-null int64# 2 sex 891 non-null object# 3 age 714 non-null float64# 4 sibsp 891 non-null int64# 5 parch 891 non-null int64# 6 fare 891 non-null float64# 7 embarked 889 non-null object# 8 class 891 non-null category# 9 who 891 non-null object# 10 adult_male 891 non-null bool# 11 deck 203 non-null category# 12 embark_town 889 non-null object# 13 alive 891 non-null object# 14 alone 891 non-null bool# dtypes: bool(2), category(2), float64(2), int64(4), object(5)# memory usage: 80.7+ KB# None
从返回结果来看,该数据集有891行和15列;其中age值存在的有714个,deck值存在的有203个。下面将定义几个函数,分别用于计算数据中有多少个null或NaN值,以及每一列或每一行数据完整案例所占的百分比。
# 使用NumPy库的sum函数import numpy as npimport pandas as pddef count_missing(vec):"""计算缺失值的个数"""# 根据值是否缺失获取一个由True/False值组成的向量null_vec = pd.isnull(vec)# 得到null_vec中null值的个数# null值对应为True,True的值为1null_count = np.sum(null_vec)# 返回缺失值的个数return null_countdef prop_missing(vec):"""计算缺失值的占比"""# 调用前面定义的函数计算缺失值个数missing_num = count_missing(vec)# 获取元素总个数total_num = vec.size# 返回缺失值占比return missing_num / total_numdef prop_complete(vec):"""计算完整值的占比"""# 先调用前面定义的函数计算缺失值占比# 用1减去缺失值占比return 1 - prop_missing(vec)
下面把前面定义好的函数应用于数据:
# 按列计算cmis_col = titanic.apply(count_missing)pmis_col = titanic.apply(prop_missing)pcom_col = titanic.apply(prop_complete)print(cmis_col)print(pmis_col)print(pcom_col)# 按行计算cmis_row = titanic.apply(count_missing, axis=1)pmis_row = titanic.apply(prop_missing, axis=1)pcom_row = titanic.apply(prop_complete, axis=1)print(cmis_row.head())print(pmis_row.head())print(pcom_row.head())# 统计数据中分别有多少行包含多少个缺失值print(cmis_row.value_counts())# 将统计的缺失值结果作为新列加入到原来的数据集中titanic['num_missing'] = titanic.apply(count_missing, axis=1)print(titanic.head())# 根据num_missing列对数据集进行筛选print(titanic.loc[titanic.num_missing > 1, :].sample(10))
2.4. lambda函数
有些时候,apply方法中使用的函数非常简单,单独去定义该函数就会显得比较麻烦,此时可以使用lambda匿名函数的形式应用:
import pandas as pd# 创建一个简单的包含两列的DataFramedf = pd.DataFrame({'a': [10, 20, 30],'b': [20, 30, 40]})print(df.loc[:, 'a'].apply(lambda x: x ** 2))
编写匿名函数需要使用lambda关键字,在向apply传递的过程中与定义好的函数一致,都是将整行或整列数据作为第一个参数传入到函数中。虽然lambda函数也可以编写比较复杂的函数体,但是通常只在需要单行计算时才使用lambda函数。如果lambda函数中包含过多代码,会影响代码的可阅读性。
