import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
plt.rc('font', family='SimHei', size=13)
%matplotlib inline
import warnings
warnings.filterwarnings('ignore')
pd.__version__
'2.1.1'
1.1 函数应用
1.1.1 apply
apply()
作为一种可自定义的函数,可以对series和dataframe的行列进行自定义函数操作并返回结果。对于分组、窗口、重采样下的dataframe子集等场景也同样适用,是复杂逻辑实现的一个非常强力的方法。
需要注意的是,apply()
在对series和dataframe的用法上有所差别:
- series:apply操作的对象是series的每个元素,不可设置行列轴方向;
- dataframe:apply操作的对象是dataframe中的每一列或者每一行,可以设置行列方向。
df_english=pd.DataFrame(
{
"student": ["John", "James", "Jennifer"],
"gender": ["male", "male", "female"],
"score": [20, 30, 30],
"subject": "english"
}
)
df_math=pd.DataFrame(
{
"student": ["John", "James", "Jennifer"],
"gender": ["male", "male", "female"],
"score": [90, 100, 95],
"subject": "math"
}
)
df = pd.concat([df_english, df_math],ignore_index=True)
df = df.sort_values(['student','subject']).reset_index(drop=True)
df
|
student |
gender |
score |
subject |
0 |
James |
male |
30 |
english |
1 |
James |
male |
100 |
math |
2 |
Jennifer |
female |
30 |
english |
3 |
Jennifer |
female |
95 |
math |
4 |
John |
male |
20 |
english |
5 |
John |
male |
90 |
math |
Series
df['score'].apply(lambda x:x-3 if x>90 else x)
0 30
1 97
2 30
3 92
4 20
5 90
Name: score, dtype: int64
DataFrame
def col(x):
if x.name=='score':
return x+5
else:
return x
df = df.apply(col, axis=0)
df
|
student |
gender |
score |
subject |
0 |
James |
male |
35 |
english |
1 |
James |
male |
105 |
math |
2 |
Jennifer |
female |
35 |
english |
3 |
Jennifer |
female |
100 |
math |
4 |
John |
male |
25 |
english |
5 |
John |
male |
95 |
math |
def row(x):
if x['subject']=='english':
para=1.1
else:
para=1.2
return x['score']*para
df['new_score'] = df.apply(row, axis=1,result_type='expand')
df
|
student |
gender |
score |
subject |
new_score |
0 |
James |
male |
35 |
english |
38.5 |
1 |
James |
male |
105 |
math |
126.0 |
2 |
Jennifer |
female |
35 |
english |
38.5 |
3 |
Jennifer |
female |
100 |
math |
120.0 |
4 |
John |
male |
25 |
english |
27.5 |
5 |
John |
male |
95 |
math |
114.0 |
传入参数
函数参数需要在args参数中设置,以元组方式args=(x1, x2, ...)
完成。
def score_bias(x, bias):
if x > 90:
return x+bias
else:
return x
df["score"] = df["score"].apply(score_bias, args=(-3,))
df
|
student |
gender |
score |
subject |
new_score |
0 |
James |
male |
35 |
english |
38.5 |
1 |
James |
male |
102 |
math |
126.0 |
2 |
Jennifer |
female |
35 |
english |
38.5 |
3 |
Jennifer |
female |
97 |
math |
120.0 |
4 |
John |
male |
25 |
english |
27.5 |
5 |
John |
male |
92 |
math |
114.0 |
传入关键字
传入关键字english=0
和math=1
。函数内通过kwargs['english']=0
和kwargs['math']=1
def subject_map(x, **kwargs):
return kwargs[x]
df["subject_no"] = df["subject"].apply(subject_map, english=0, math=1)
df
|
student |
gender |
score |
subject |
new_score |
subject_no |
0 |
James |
male |
35 |
english |
38.5 |
0 |
1 |
James |
male |
102 |
math |
126.0 |
1 |
2 |
Jennifer |
female |
35 |
english |
38.5 |
0 |
3 |
Jennifer |
female |
97 |
math |
120.0 |
1 |
4 |
John |
male |
25 |
english |
27.5 |
0 |
5 |
John |
male |
92 |
math |
114.0 |
1 |
1.1.2 applymap
applymap()
应用于dataframe时(且只能用于dataframe),操作对象是每个元素,即接收一个标量元素经过函数处理后再返回一个标量函数,是点对点的操作。
def el_cook(x):
if isinstance(x,str):
return 's_'+x
else:
return str(x)
df.applymap(el_cook)
|
student |
gender |
score |
subject |
new_score |
subject_no |
0 |
s_James |
s_male |
35 |
s_english |
38.5 |
0 |
1 |
s_James |
s_male |
102 |
s_math |
126.0 |
1 |
2 |
s_Jennifer |
s_female |
35 |
s_english |
38.5 |
0 |
3 |
s_Jennifer |
s_female |
97 |
s_math |
120.0 |
1 |
4 |
s_John |
s_male |
25 |
s_english |
27.500000000000004 |
0 |
5 |
s_John |
s_male |
92 |
s_math |
114.0 |
1 |
1.1.3 map
map()
只能应用于series,可以将series元素通过字典或函数进行映射操作。
- arg: 映射参数,可以是映射子类如字典、函数或series;
- na_action:对na空值的处理;
None
:默认传给函数处理;
ignore
:忽略并直接传播空值,而不传给函数处理。
字典映射
GENDER_ENCODING= {
"male": 0,
"female": 1
}
df['gender_map'] = df["gender"].map(GENDER_ENCODING)
df
|
student |
gender |
score |
subject |
new_score |
subject_no |
gender_map |
0 |
James |
male |
35 |
english |
38.5 |
0 |
0 |
1 |
James |
male |
102 |
math |
126.0 |
1 |
0 |
2 |
Jennifer |
female |
35 |
english |
38.5 |
0 |
1 |
3 |
Jennifer |
female |
97 |
math |
120.0 |
1 |
1 |
4 |
John |
male |
25 |
english |
27.5 |
0 |
0 |
5 |
John |
male |
92 |
math |
114.0 |
1 |
0 |
函数映射
缺点是不能像apply()
一样传入参数和关键字。大数据时映射效率更高。
df['score'].map(np.sqrt).to_frame()
|
score |
0 |
5.916080 |
1 |
10.099505 |
2 |
5.916080 |
3 |
9.848858 |
4 |
5.000000 |
5 |
9.591663 |
df['student'].map(list).to_frame()
|
student |
0 |
[J, a, m, e, s] |
1 |
[J, a, m, e, s] |
2 |
[J, e, n, n, i, f, e, r] |
3 |
[J, e, n, n, i, f, e, r] |
4 |
[J, o, h, n] |
5 |
[J, o, h, n] |
df['score'].map(lambda x:x-3 if x>90 else x)
0 35
1 99
2 35
3 94
4 25
5 89
Name: score, dtype: int64
1.1.4 transform
transform()
是一种转换函数,其特点是返回结果与自身的形状相同,即前后的shape形状保持不变。因此,transform()
的返回结果与自身形状相同,所以不支持有降维功能的函数,比如聚合函数min
、mean
、std
。
cols = ["C_0", "C_1", "C_2", "C_3"]
np.random.seed=123
df = pd.DataFrame(np.random.randint(1, 5, size = (5,4)), columns=cols)
df
|
C_0 |
C_1 |
C_2 |
C_3 |
0 |
3 |
3 |
3 |
3 |
1 |
4 |
2 |
3 |
1 |
2 |
1 |
1 |
1 |
4 |
3 |
3 |
2 |
1 |
2 |
4 |
4 |
1 |
2 |
2 |
单个函数
传入的单个函数可以是库函数、自定义函数或匿名函数。
df.transform(np.exp).transform(lambda x:round(x,2))
|
C_0 |
C_1 |
C_2 |
C_3 |
0 |
20.09 |
20.09 |
20.09 |
20.09 |
1 |
54.60 |
7.39 |
20.09 |
2.72 |
2 |
2.72 |
2.72 |
2.72 |
54.60 |
3 |
20.09 |
7.39 |
2.72 |
7.39 |
4 |
54.60 |
2.72 |
7.39 |
7.39 |
多个函数
当transform()
传入多个函数是列表形式时,会对dataframe所有列依次进行各函数的转换操作。注意如果有名字相同的函数,只有最后一个函数生效,这个不同于agg()
函数可以使得多个匿名函数同时生效。
df.transform([np.square, np.sqrt]).transform(lambda x:round(x,2))
|
C_0 |
C_1 |
C_2 |
C_3 |
|
square |
sqrt |
square |
sqrt |
square |
sqrt |
square |
sqrt |
0 |
9 |
1.73 |
9 |
1.73 |
9 |
1.73 |
9 |
1.73 |
1 |
16 |
2.00 |
4 |
1.41 |
9 |
1.73 |
1 |
1.00 |
2 |
1 |
1.00 |
1 |
1.00 |
1 |
1.00 |
16 |
2.00 |
3 |
9 |
1.73 |
4 |
1.41 |
1 |
1.00 |
4 |
1.41 |
4 |
16 |
2.00 |
1 |
1.00 |
4 |
1.41 |
4 |
1.41 |
1.1.5 pipe
前面的applymap()
是元素级、apply()
和transform()
是行列级应用的函数,pipe()
则是表格级的应用函数,称为管道函数。
单个函数
df.pipe(np.exp).pipe(lambda x: round(x, 2))
|
C_0 |
C_1 |
C_2 |
C_3 |
0 |
20.09 |
20.09 |
20.09 |
20.09 |
1 |
54.60 |
7.39 |
20.09 |
2.72 |
2 |
2.72 |
2.72 |
2.72 |
54.60 |
3 |
20.09 |
7.39 |
2.72 |
7.39 |
4 |
54.60 |
2.72 |
7.39 |
7.39 |
链式调用
df.pipe(np.square).\
pipe(np.multiply, 1.5).\
pipe(np.add, 8)
|
C_0 |
C_1 |
C_2 |
C_3 |
0 |
21.5 |
21.5 |
21.5 |
21.5 |
1 |
32.0 |
14.0 |
21.5 |
9.5 |
2 |
9.5 |
9.5 |
9.5 |
32.0 |
3 |
21.5 |
14.0 |
9.5 |
14.0 |
4 |
32.0 |
9.5 |
14.0 |
14.0 |
特殊传参方式
pipe()
默认情况下会将dataframe传给调用函数的第一个参数,但一些函数的第一个参数并不是用来接收dataframe输入数据的,如果直接将函数传到pipe()
中会提示报错。
为了解决这个问题,pipe()
中规定了一种特殊的参数传递方法,是**元组(callable, data_keyword)**的形式。
callable
:指定pipe()
中调用的函数;
data_keyword
:指定将dataframe传给函数中的哪一个参数。
def spcl(num, df):
return df.add(num)
df.pipe((spcl,'df'), 2)
|
C_0 |
C_1 |
C_2 |
C_3 |
0 |
5 |
5 |
5 |
5 |
1 |
6 |
4 |
5 |
3 |
2 |
3 |
3 |
3 |
6 |
3 |
5 |
4 |
3 |
4 |
4 |
6 |
3 |
4 |
4 |
1.2 表达式求值
1.2.1 eval
eval()
是一个神奇的函数,可以通过字符串表达式的方式对series和dataframe进行计算和解析操作。其有两大优势:
- 对数据较大的dataframe对象操作更高效;
- 对复杂的算术和布尔运算更快速,因为后端计算引擎默认是
numexpr
。
eval()
支持以下算术操作:
- 算术运算:除左移
<<
和右移>>
运算符外的算术运算
- 比较操作:包括链式比较,比如,
2 < df < df2
- 布尔运算:例如,
df < df2 and df3 < df4 or not df_bool
- 列表和元组:如
[1, 2]
,(1, 2)
- 属性访问,如
df.a
- 下标表达式:如
df[0]
- 变量评估:如
pd.eval('df')
- 数学函数:如
sin
、cos
等
eval()
不允许使用Python语法:
- 表达式
- 数学函数以外的函数调用
is
is not
操作
if
表达式
lambda
表达式
list/set/dict
comprehension
- literal的
dict
和set
表达式
yield
表达
- 生成器表达式
- 仅包含标量值的布尔表达式
- 声明
eval()
有两种函数形式
pandas.eval()
dataframe.eval()
,是前者的高级封装。
单列变量
pd.eval("C_4 = (df.C_0 > 1) & (df.C_2 == 4)", target=df)
|
C_0 |
C_1 |
C_2 |
C_3 |
C_4 |
0 |
3 |
3 |
3 |
3 |
False |
1 |
4 |
2 |
3 |
1 |
False |
2 |
1 |
1 |
1 |
4 |
False |
3 |
3 |
2 |
1 |
2 |
False |
4 |
4 |
1 |
2 |
2 |
False |
df.eval("C_4 = (C_0 > 1) & (C_2 == 4)")
|
C_0 |
C_1 |
C_2 |
C_3 |
C_4 |
0 |
3 |
3 |
3 |
3 |
False |
1 |
4 |
2 |
3 |
1 |
False |
2 |
1 |
1 |
1 |
4 |
False |
3 |
3 |
2 |
1 |
2 |
False |
4 |
4 |
1 |
2 |
2 |
False |
多列变量
df.eval(
"""
C_4 = C_0 + C_1
C_5 = C_1 + C_2
C_6 = C_2 + C_3
"""
)
|
C_0 |
C_1 |
C_2 |
C_3 |
C_4 |
C_5 |
C_6 |
0 |
3 |
3 |
3 |
3 |
6 |
6 |
6 |
1 |
4 |
2 |
3 |
1 |
6 |
5 |
4 |
2 |
1 |
1 |
1 |
4 |
2 |
2 |
5 |
3 |
3 |
2 |
1 |
2 |
5 |
3 |
3 |
4 |
4 |
1 |
2 |
2 |
5 |
3 |
4 |
局部变量
字符串表达式中可加入局部变量参与计算,通过@
前缀标识完成,该前缀方法只能应用于dataframe.eval()
函数,对pd.eval()
不生效。
a = 5
b = 2
df.eval("C_4 = C_0 * @a + @b")
|
C_0 |
C_1 |
C_2 |
C_3 |
C_4 |
0 |
3 |
3 |
3 |
3 |
17 |
1 |
4 |
2 |
3 |
1 |
22 |
2 |
1 |
1 |
1 |
4 |
7 |
3 |
3 |
2 |
1 |
2 |
17 |
4 |
4 |
1 |
2 |
2 |
22 |
a = 5
b = 2
pd.eval("C_4 = df.C_0 * a + b", target=df)
|
C_0 |
C_1 |
C_2 |
C_3 |
C_4 |
0 |
3 |
3 |
3 |
3 |
17 |
1 |
4 |
2 |
3 |
1 |
22 |
2 |
1 |
1 |
1 |
4 |
7 |
3 |
3 |
2 |
1 |
2 |
17 |
4 |
4 |
1 |
2 |
2 |
22 |
类型解析
这里的eval()
是Python的内置方法,用来解析字符串。
a = '[1,2,3]'
type(a)
str
b=eval(a)
type(b)
list
1.2.2 query
dataframe.query()
与dataframe.eval()
一样,也是pd.eval()
的高级封装,可对dataframe对象进行查询操作。