ITPub博客

首页 > 数据库 > 数据库开发技术 > Pandas非常用技巧汇总

Pandas非常用技巧汇总

原创 数据库开发技术 作者:quincyqiang 时间:2021-03-24 08:51:52 0 删除 编辑

点击上方

蓝字

关注我们



import pandas as pd
import numpy as np
import re

P1 缺失值填充

1.1 用另一列对应行的内容填充本列缺失值

df = pd.DataFrame({'A': [1, 2, 3, 4, 5], 'B': [1, np.nan, 3, np.nan, 5]})
df

A B
0 1 1.0
1 2 NaN
2 3 3.0
3 4 NaN
4 5 5.0

假设此处我们希望用A列的内容来填充B列的缺失值。

df['B'] = df['B'].fillna(df['A']) # 简单地使用fillna就可以
df

A B
0 1 1.0
1 2 2.0
2 3 3.0
3 4 4.0
4 5 5.0

注意:由于NaN的存在,B列初始的数据类型是float,如果要变成整数,使用astype转换即可。

1.2 用本列的均值来填充本列缺失值

df = pd.DataFrame({'A': [1, np.nan, 3, np.nan, 5]})
df

A
0 1.0
1 NaN
2 3.0
3 NaN
4 5.0

假设此处我们希望用A列的均值来填充A列的缺失值。

df['A'].fillna(df['A'].mean(), inplace=True)
df

A
0 1.0
1 3.0
2 3.0
3 3.0
4 5.0

注意:此处我们使用了 inplace=True 这个参数,它与以下命令是等同的。

df['A'] = df['A'].fillna(df['A'].mean()) # inplace可以直接替换,不需要再使用赋值语句

1.3 用分组均值来填充本列缺失值

df = pd.DataFrame({'A': ['a', 'b', 'a', 'b', 'a', 'b'], 'B': [1, np.nan, 3, 3, np.nan, 5]})
df

A B
0 a 1.0
1 b NaN
2 a 3.0
3 b 3.0
4 a NaN
5 b 5.0

假设此处我们希望根据A列的分组来填充B列的缺失值。
B列中1.0, 3.0, NaN属于A列中的a组(故填充均值2.0),而NaN, 3.0, 5.0属于A列中的B组(故填充均值4.0)。

df['B'] = df.groupby('A')['B'].transform(lambda x: x.fillna(x.mean()))
df

A B
0 a 1.0
1 b 4.0
2 a 3.0
3 b 3.0
4 a 2.0
5 b 5.0

transform是个非常有用的函数,它所得出的直接结果是一列数据。通常我们可以认为groupby后面跟各种aggregation函数(mean, sum, ...)后,我们会得到一个“缩水”的结果,表的行数会变成分组的个数。而groupby后面跟transform的话,表的行数会保持不变,相当于没有“缩水”。详见:
https://www.jianshu.com/p/509d7b97088c

1.4 行列缺失情况统计

df = pd.DataFrame({'A': [1, np.nan, 3, 3, np.nan, 5], 
                   'B': [1, np.nan, np.nan, 3, np.nan, 5],
                   'C': [1, np.nan, 2, 3, 4, 5]})
df

A B C
0 1.0 1.0 1.0
1 NaN NaN NaN
2 3.0 NaN 2.0
3 3.0 3.0 3.0
4 NaN NaN 4.0
5 5.0 5.0 5.0

假设我们分别需要按行和按列统计NaN的数量。

df.isnull().sum() # 按列统计
A    2
B 3
C 1
dtype: int64
df.isnull().sum(axis=1) # 按行统计
0    0
1 3
2 1
3 0
4 2
5 0
dtype: int64
df.isnull().sum().sum() # 总体统计
6

P2 groupby相关

2.1 保留聚合列

df = pd.DataFrame({'A': ['a', 'b', 'a', 'b', 'a', 'b'], 'B': [1, 2, 3, 3, 4, 5]})
df

A B
0 a 1
1 b 2
2 a 3
3 b 3
4 a 4
5 b 5

我们按照A列中的分组进行聚合,并对B列进行求和,正常情况下我们会得到一个Series,而A列的内容被加入了索引中。

df.groupby('A')['B'].sum()
A
a 8
b 10
Name: B, dtype: int64

假设我们希望保留A列的内容,不使其进入索引,以便我们后续进行merge等操作,我们可以简单地使用 as_index=False 这一参数。

df.groupby('A', as_index=False)['B'].sum()

A B
0 a 8
1 b 10

2.2 取前n项

df = pd.DataFrame({'A': ['a', 'a', 'a', 'a', 'b', 'b', 'b'], 'B': [1, 3, 2, 4, 5, 2, 3]})
df

A B
0 a 1
1 a 3
2 a 2
3 a 4
4 b 5
5 b 2
6 b 3

假设我们想按照A列聚合后分别取每组的前2项,即a组取1、3,b组取5,2,我们可以利用head:

df.groupby('A')['B'].head(2)
0    1
1 3
4 5
5 2
Name: B, dtype: int64

注意:此处无论你是否采用 as_index=False 这一参数,结果都不会变化,如果你需要保留聚合列(用于后续merge等),请按照以下写法:

df.groupby('A', group_keys=False).apply(lambda x: x.head(2)) # 注意设置group_keys=False,否则返回多重索引

A B
0 a 1
1 a 3
4 b 5
5 b 2

这一技巧可以用于当我们想取最大或最小的前n项时,我们可以先进行排序,然后用head来选取:

df

A B
0 a 1
1 a 3
2 a 2
3 a 4
4 b 5
5 b 2
6 b 3
df.sort_values(['A', 'B'], ascending=False, inplace=True) # 第1步
df.groupby('A', group_keys=False).apply(lambda x: x.head(2)) # 第2步

A B
3 a 4
1 a 3
4 b 5
6 b 3

可以放心的是,即使你取的前n项的n超过了某个分组中成员数量的最大值,也不会报错。例如这里我取n=4,而b组只有3个,则结果中b组只返回3项。

df.groupby('A', group_keys=False).apply(lambda x: x.head(4)) 

A B
3 a 4
1 a 3
2 a 2
0 a 1
4 b 5
6 b 3
5 b 2

2.3 取第n项

df = pd.DataFrame({'A': ['a', 'a', 'a', 'a', 'b', 'b', 'b'], 'B': [1, 3, 2, 4, 5, 2, 3]})
df

A B
0 a 1
1 a 3
2 a 2
3 a 4
4 b 5
5 b 2
6 b 3

假设我们想按照A列聚合后分别取每组的第2项,即a组取3,b组取2,我们可以利用nth:

df.groupby('A')['B'].nth(1) # nth计数从0开始!!!
A
a 3
b 2
Name: B, dtype: int64

注意:nth与head不同,前者计数从0开始,后者从1开始。
另外,此处无论你是否采用 as_index=False 这一参数,结果都不会变化,如果你需要保留聚合列(用于后续merge等),请按照以下写法:

df.groupby('A', as_index=False).apply(lambda x: x.iloc[1]) 

A B
0 a 3
1 b 2

但这种方法有一个缺陷,当你所选取的n超过某个分组中成员数量的最大值时,就会报错,比如我取每组的第4项,而b组只有3个,就会报错。我们可以用以下命令取而代之:

df[df.groupby('A').cumcount() == 3] # 同样计数从0开始,此处即取第4项

A B
3 a 4

cumcount即累计计数,从0开始,每看到一条就+1,因此利用cumcount可以巧妙地解决这个问题。
这一技巧可以运用于我们想取最大或最小的第n项,同样先进行排序,再选取:

df

A B
0 a 1
1 a 3
2 a 2
3 a 4
4 b 5
5 b 2
6 b 3
df.sort_values(['A', 'B'], ascending=False, inplace=True) # 第1步
df[df.groupby('A').cumcount() == 1] # 第2步

A B
6 b 3
1 a 3

2.4 按时间特征及其他特征聚合

df = pd.DataFrame()
df['date'] = list(pd.date_range('2018-01-01', '2018-01-14')) * 2
df['shop'] = ['shop_A'] * 14 + ['shop_B'] * 14
df['sales'] = [1, 2, 5, 3, 1, 5, 2, 3, 1, 1, 0, 0, 4, 5, 
               5, 1, 1, 2, 3, 4, 4, 0, 1, 2, 1, 2, 0, 5]
df

date shop sales
0 2018-01-01 shop_A 1
1 2018-01-02 shop_A 2
2 2018-01-03 shop_A 5
3 2018-01-04 shop_A 3
4 2018-01-05 shop_A 1
5 2018-01-06 shop_A 5
6 2018-01-07 shop_A 2
7 2018-01-08 shop_A 3
8 2018-01-09 shop_A 1
9 2018-01-10 shop_A 1
10 2018-01-11 shop_A 0
11 2018-01-12 shop_A 0
12 2018-01-13 shop_A 4
13 2018-01-14 shop_A 5
14 2018-01-01 shop_B 5
15 2018-01-02 shop_B 1
16 2018-01-03 shop_B 1
17 2018-01-04 shop_B 2
18 2018-01-05 shop_B 3
19 2018-01-06 shop_B 4
20 2018-01-07 shop_B 4
21 2018-01-08 shop_B 0
22 2018-01-09 shop_B 1
23 2018-01-10 shop_B 2
24 2018-01-11 shop_B 1
25 2018-01-12 shop_B 2
26 2018-01-13 shop_B 0
27 2018-01-14 shop_B 5

通常情况下,如果我们想按照时间来聚合(从日到周),我们可以使用resample。但在这里,我们希望按照date和shop来聚合,即看看每个店每周的总销量分别是多少,这时候resample就不够用了,我们需要使用pd.Grouper:

df.groupby([pd.Grouper(key='date', freq='7d'), 'shop'])['sales'].sum()
date        shop  
2018-01-01 shop_A 19
shop_B 20
2018-01-08 shop_A 14
shop_B 11
Name: sales, dtype: int64

Grouper中的key就是需要聚合的时间列,而freq就是按照怎样的时间跨度来聚合。我们按照这个Grouper和shop进行聚合就完成了我们所想要的操作,如果我们希望能展平index的话,直接reset_index即可:

df.groupby([pd.Grouper(key='date', freq='7d'), 'shop'])['sales'].sum().reset_index()

date shop sales
0 2018-01-01 shop_A 19
1 2018-01-01 shop_B 20
2 2018-01-08 shop_A 14
3 2018-01-08 shop_B 11

2.5 对不同的列进行不同类型的聚合

df=pd.DataFrame({'A':['g1', 'g1', 'g2', 'g2', 'g1'],'B':[3, 2, 3, 4, 2],'C':[1, 2, 1, 3, 4]})
df

A B C
0 g1 3 1
1 g1 2 2
2 g2 3 1
3 g2 4 3
4 g1 2 4

假设我们希望根据A列中的分组,对B列进行求和,而对C列求均值。我们可以定义一个字典,分别写出列名和相应的操作,然后通过agg函数完成操作。

agg_dic = {'B': 'sum', 'C': 'mean'}
df.groupby('A', as_index=False).agg(agg_dic)

A B C
0 g1 7 2.333333
1 g2 7 2.000000

2.6 分组后装入列表

df=pd.DataFrame({'A':['g1', 'g1', 'g2', 'g2', 'g1'],'B':[3, 2, 3, 4, 2]})
df

A B
0 g1 3
1 g1 2
2 g2 3
3 g2 4
4 g1 2

假设我们想根据A列分组,并将每组对应的元素放入列表中(比如g1对应[3, 2, 2])。

df.groupby('A')['B'].apply(list).reset_index()

A B
0 g1 [3, 2, 2]
1 g2 [3, 4]

2.7 列表元素拆出

df=pd.DataFrame({'A':['g1', 'g2'],'B':[[3, 2, 1], [4, 2]]})
df

A B
0 g1 [3, 2, 1]
1 g2 [4, 2]

与2.6中的操作相反,这次我们希望g1, g2分别于其对应的B列中列表内的每个元素单独形成一行(g1-3, g1-2, g1-1, g2-4, g2-2)。

vals = df['B'].values.tolist() # 将B列的内容转为列表
rs = [len(r) for r in vals] # 获取B列中每个列表的长度
a = np.repeat(df['A'], rs) # A列中的每个元素重复rs中的对应次数
df2 = pd.DataFrame(np.column_stack((a, np.concatenate(vals))), columns=df.columns) # column_stack为列合并(行数不变)
df2

A B
0 g1 3
1 g1 2
2 g1 1
3 g2 4
4 g2 2

2.8 组内打乱

df = pd.DataFrame({'A':['g1', 'g1', 'g1', 'g1', 'g2', 'g2', 'g2'],'B':[1, 2, 3, 4, 5, 6, 7]})
df

A B
0 g1 1
1 g1 2
2 g1 3
3 g1 4
4 g2 5
5 g2 6
6 g2 7

假设我们想分别根据A列的分组打乱B的顺序,即分别打乱1、2、3、4(g1组)和5、6、7(g2组),我们可以利用transform来完成:

df['B'] = df.groupby('A')['B'].transform(np.random.permutation)
df

A B
0 g1 1
1 g1 2
2 g1 4
3 g1 3
4 g2 7
5 g2 6
6 g2 5

2.9 组内出现次数相关

df = pd.DataFrame({'A': ['a', 'a', 'a', 'a', 'a', 'b', 'b', 'b', 'b', 'b', 'b'], 
                   'B': [1, 3, 2, 4, 3, 5, 2, 3, 2, 2, 2]})
df

A B
0 a 1
1 a 3
2 a 2
3 a 4
4 a 3
5 b 5
6 b 2
7 b 3
8 b 2
9 b 2
10 b 2

假设我们想知道根据A列分组后,查看每组内B列元素出现次数最多的元素和其出现的次数,我们可以通过value_counts来实现。首先,我们可以查看B列每个元素出现的次数:

df.groupby('A')['B'].value_counts()
A  B
a 3 2
1 1
2 1
4 1
b 2 4
3 1
5 1
Name: B, dtype: int64

如果我们要选择次数最多就使用max()和idxmax():

df.groupby('A')['B'].apply(lambda x: x.value_counts().idxmax())
A
a 3
b 2
Name: B, dtype: int64

通过apply与value_counts和idxmax的结合,我们看到a组和b组中出现次数最多的分别是3和2。

df.groupby('A')['B'].apply(lambda x: x.value_counts().max())
A
a 2
b 4
Name: B, dtype: int64

而通过apply与value_counts和max的结合,我们看到a组和b组中出现次数最多的元素分别出现了2次和4次。


P3 行列操作

3.1 复制多行

df = pd.DataFrame({'A': ['a', 'b'], 'B': [1, 3]})
df

A B
0 a 1
1 b 3

假设由于某些原因,我们需要对行进行复制,一种是按块复制,即ab ab ab:

pd.concat([df]*4) # 复制4次

A B
0 a 1
1 b 3
0 a 1
1 b 3
0 a 1
1 b 3
0 a 1
1 b 3

另一种是按行复制,即 aaa bbb:

pd.concat([df]*4).sort_values('A').reset_index(drop=True) # 复制后按A列排序,并重设索引以达到效果

A B
0 a 1
1 a 1
2 a 1
3 a 1
4 b 3
5 b 3
6 b 3
7 b 3

3.2 合并多列内容(文本)

df = pd.DataFrame({'A': [1988, 1993], 'B': ['02', '03'], 'C':[21, 13]})
df

A B C
0 1988 02 21
1 1993 03 13

假设我们希望每行的三个单元格合并成形如 1998-02-21 的形式。

df['D'] = df['A'].astype(str) + '-' + df['B'].astype(str) + '-' + df['C'].astype(str)
df

A B C D
0 1988 02 21 1988-02-21
1 1993 03 13 1993-03-13

注意:此处由于某些单元格中存在数值,所以我们要先用astype将其转化为字符串,然后合并,否则可能报错。

3.3 拆分列内容(文本)

df = pd.DataFrame({'A': ['1988-02-21', '1993-03-13']})
df

A
0 1988-02-21
1 1993-03-13

假设与之前相反,我们希望把A列拆分为年、月、日3列,可以进行如下操作:

df2 = pd.DataFrame(df['A'].str.split('-').tolist(), columns=['year', 'month', 'day'])
df2

year month day
0 1988 02 21
1 1993 03 13

逐段分解上述命令:
(1)首先,我们使用了df['A'].str,这样就可以进行字符操作,以便我们后续split();
(2)我们利用tolist()将分割后的字符串装入列表;
(3)我们重新创建了一个表,而columns=['year', 'month', 'day']指定了新表的列名。

3.4 拆分列内容(文本,拆分后不定长)

df = pd.DataFrame({'A': ['88-02-21-00-23', '93-03-13', '00-51-03-13']})
df

A
0 88-02-21-00-23
1 93-03-13
2 00-51-03-13

假设此处我们希望按照'-'为分隔符来拆分A列的内容,但我们发现每行分割后元素数量并不相同(5个、3个、4个),用之前的操作会很麻烦,我们可以用如下操作(但是无法直接命名拆分后的列):

df['A'].str.split('-', expand=True)

0 1 2 3 4
0 88 02 21 00 23
1 93 03 13 None None
2 00 51 03 13 None

3.5 选取包含特定文本的列

df = pd.DataFrame({'A': ['highest', 'good', 'just', 'newest', 'newer', 'estimate']})
df

A
0 highest
1 good
2 just
3 newest
4 newer
5 estimate

假设我们需要选取字符串中包含'est'的行,我们依然会用到df['A'].str来进行字符级操作:

df[df['A'].str.contains('est')]

A
0 highest
3 newest
5 estimate

事实上,contains里可以包含各种正则表达式:

df = pd.DataFrame({'A': ['yes ok', 'yesok', 'yes 2 ok', 'okok', 'yes yes', 'ok yes']})
df

A
0 yes ok
1 yesok
2 yes 2 ok
3 okok
4 yes yes
5 ok yes
df[df['A'].str.contains(re.compile('yes.+ok'))]

A
0 yes ok
2 yes 2 ok

3.6 clip的用途

df = pd.DataFrame({'Name': ['Apple', 'Peach', 'Melon', 'Grape', 'Banana'], 'Sales': [2, 0, 5, 1, -1]})
df

Name Sales
0 Apple 2
1 Peach 0
2 Melon 5
3 Grape 1
4 Banana -1

假设此处我们sales转换为多分类,即≤0(没卖出去或惨遭退货),卖出去1件,和卖出1件以上,以便后续处理,我们可以简单地使用clip来完成。这在某些低销量商品转化为分类问题的过程中可能会用到。

df['label'] = df['Sales'].clip(0, 2) # 下限为0,上限为2
df

Name Sales label
0 Apple 2 2
1 Peach 0 0
2 Melon 5 2
3 Grape 1 1
4 Banana -1 0

3.7 值替换

df = pd.DataFrame({'A': ['a', 'b', 'a', 'b', 'a', 'b'], 'B': [1, 2, 3, 3, 3, 5]})
df

A B
0 a 1
1 b 2
2 a 3
3 b 3
4 a 3
5 b 5

假设此处我们需要将A列中的内容进行替换,'a'替换为'Male','b'替换为'Female',我们可以利用字典和map命令来进行。

dic = {'a': 'Male', 'b': 'Female'}
df['A'] = df['A'].map(dic)
df

A B
0 Male 1
1 Female 2
2 Male 3
3 Female 3
4 Male 3
5 Female 5

然而,使用map有一个问题,如果A列中有某些值不在字典中,这些值会变成NaN,如下所示:

df = pd.DataFrame({'A': ['a', 'b', 'a', 'b', 'a', 'b', 'c', 'd'], 'B': [1, 2, 3, 3, 3, 5, 4, 5]})
df['A'] = df['A'].map(dic)
df

A B
0 Male 1
1 Female 2
2 Male 3
3 Female 3
4 Male 3
5 Female 5
6 NaN 4
7 NaN 5

可以看到A列中的'c'和'd'变成了NaN,要解决这一问题,我们可以用字典与replace()函数的结合。

df = pd.DataFrame({'A': ['a', 'b', 'a', 'b', 'a', 'b', 'c', 'd'], 'B': [1, 2, 3, 3, 3, 5, 4, 5]})
df['A'] = df['A'].replace(dic)
df

A B
0 Male 1
1 Female 2
2 Male 3
3 Female 3
4 Male 3
5 Female 5
6 c 4
7 d 5

可以看到'c'和'd'被保留了下来。

3.8 最值查询

df = pd.DataFrame({'Name': ['Apple', 'Peach', 'Melon', 'Grape', 'Banana'], 'Sales': [2, 0, 5, 1, -1]})
df

Name Sales
0 Apple 2
1 Peach 0
2 Melon 5
3 Grape 1
4 Banana -1

假设我们分别需要知道Sales最大值和最小值所对应的Name。

df.iloc[df['Sales'].idxmax()] # idxmax即返回最大值对应的索引,最小值使用idxmin
Name     Melon
Sales 5
Name: 2, dtype: object

上述命令返回的是改行的所有内容,如果我们只需要知道Name,加上列名即可。

df['Name'].iloc[df['Sales'].idxmax()]
'Melon'

3.9 选出第N大/第N小

df = pd.DataFrame({'A': [1, 1, 2, 2, 3, 4, 5, 6, 7, 7, 8], 'B': [2, 4, 2, 1, 4, 5, 11, 23, 42, 1, 22]})
df

A B
0 1 2
1 1 4
2 2 2
3 2 1
4 3 4
5 4 5
6 5 11
7 6 23
8 7 42
9 7 1
10 8 22

假设我们想选出A中第二大的值(7)所对应的行,我们可以使用rank:

df[df['A'].rank(method='dense', ascending=False) == 2]

A B
8 7 42
9 7 1

注意:此处rank中的参数method我们设为"dense",意味着无论有多少并列,我们的排名数始终为1、2、3……而不会因为并列而跳过某些值

如果要选择第三小的值:

df[df['A'].rank(method='dense', ascending=True) == 3]

A B
4 3 4

3.10 选取特定数据类型的行

df = pd.DataFrame({'A': [1988, 1993, 'noise', 'noise2', 2018, 'noise3']})
df

A
0 1988
1 1993
2 noise
3 noise2
4 2018
5 noise3
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 6 entries, 0 to 5
Data columns (total 1 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 A 6 non-null object
dtypes: object(1)
memory usage: 176.0+ bytes
df

A
0 1988
1 1993
2 noise
3 noise2
4 2018
5 noise3

df中混杂有数值与字符串,假设我们想将他们分离开来,比如我们只需要选取数值,如果我们用如下命令的话会报错:

df[type(df['A']) == int]
---------------------------------------------------------------------------

KeyError Traceback (most recent call last)

~/anaconda3/lib/python3.8/site-packages/pandas/core/indexes/base.py in get_loc(self, key, method, tolerance)
2645 try:
-> 2646 return self._engine.get_loc(key)
2647 except KeyError:


pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc()


pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc()


pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()


pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()


KeyError: False


我们需要通过apply来完成选取。

df[df['A'].apply(lambda x: type(x) == int)]

A
0 1988
1 1993
4 2018

3.11 总体统计

df = pd.DataFrame({'A':[1, 2, 1, 3], 'B':[2, 1, 1, 4], 'C':[3, 4, 1, 1]})
df

A B C
0 1 2 3
1 2 1 4
2 1 1 1
3 3 4 1

我们知道通常我们对某一列进行数值统计的时候会用value_counts函数,比如统计A列有多少个1,多少个2……然而value_counts只能用于Series,并不能用于DataFrame,当我们要对全表进行数值统计时应该怎么办呢?我个人的办法是,先把df拉长成一个Series,然后再应用value_counts函数:

pd.Series(np.ravel(df)).value_counts()
1    6
4 2
3 2
2 2
dtype: int64

3.12 滑窗统计相关

df = pd.DataFrame({'signal': [1, 2, 3, 2, 5, 3, 5, 6, 3, 2, 3, 5, 6, 2, 1, 3, 2]})
df

signal
0 1
1 2
2 3
3 2
4 5
5 3
6 5
7 6
8 3
9 2
10 3
11 5
12 6
13 2
14 1
15 3
16 2

我们都知道rolling能提供滑窗功能,辅以各种统计函数就能实现滑窗均值、滑窗最值等等功能。但有时候我们的需求可能会更复杂一些,而rolling原生的统计函数并不多,不过幸好,我们有apply。假设我们要统计的窗口长度为5,每个窗口内统计最大top 3的和:

df['rolling_top3_sum'] = df['signal'].rolling(5).apply(lambda x: np.sum(np.sort(x)[-3:]))
df

signal rolling_top3_sum
0 1 NaN
1 2 NaN
2 3 NaN
3 2 NaN
4 5 10.0
5 3 11.0
6 5 13.0
7 6 16.0
8 3 16.0
9 2 14.0
10 3 14.0
11 5 14.0
12 6 14.0
13 2 14.0
14 1 14.0
15 3 14.0
16 2 11.0

rolling函数的参数自然就是窗口长度,rolling出来的结果可以看做是个array,所以我们可以对其应用各种numpy函数,这里我们拆解一下apply:首先我们先对rolling出来的array进行排序(从小到大),然后取倒数3个,最后将其加和。比如第6个窗口是[3, 2, 5, 3, 5],排序后是[2, 3, 3, 5, 5],取最后3个(最大值top 3)是[3, 5, 5],求和为13。

3.13 导出字典

df = pd.DataFrame({'A':['a', 'b', 'c', 'd'], 'B':[2, 1, 1, 4]})
df

A B
0 a 2
1 b 1
2 c 1
3 d 4

我们假设我们希望导出一个字典,以A列为键,以B列为值。我们用set_index()来指定为键的列,然后用to_dict()函数进行转换:

df.set_index('A').to_dict()
{'B': {'a': 2, 'b': 1, 'c': 1, 'd': 4}}

可以看到转换后我们想要的字典被包含在另一个字典里,而那个字典的键就是另一列(B列)的列名:

df.set_index('A').to_dict()['B']
{'a': 2, 'b': 1, 'c': 1, 'd': 4}

3.14 Count Encoding

df = pd.DataFrame({'A':[1, 1, 1, 2, 3, 1, 3, np.nan], 'B':[1, 1, 1, 1, 1, 2, 2, 2]})
df

A B
0 1.0 1
1 1.0 1
2 1.0 1
3 2.0 1
4 3.0 1
5 1.0 2
6 3.0 2
7 NaN 2

假设我们希望有一列标记A中每个元素出现的次数(即Count Encoding),一个比较简单的方法是先将value_counts的结果转化为dict,再利用map函数:

tmp = df['A'].value_counts().to_dict()
df['A_cnt'] = df['A'].map(tmp)
df

A B A_cnt
0 1.0 1 4.0
1 1.0 1 4.0
2 1.0 1 4.0
3 2.0 1 1.0
4 3.0 1 2.0
5 1.0 2 4.0
6 3.0 2 2.0
7 NaN 2 NaN

值得注意的是,该方法并不支持nan,如果需要对nan也进行统计,则需先用fillna进行填充。

3.15 检查单调性

df = pd.DataFrame({'A':[1, 1, 2, 3, 5], 'B':[2, 1, 1, 2, 4], 'C':[5, 4, 3, 2, 1]})
df

A B C
0 1 2 5
1 1 1 4
2 2 1 3
3 3 2 2
4 5 4 1

假设我们希望检查某一列是否单调递增,我们可以使用is_monotonic来查看。注意,is_monotonic是pd.Series的属性:

print(df['A'].is_monotonic)
print(df['B'].is_monotonic)
print(df['C'].is_monotonic)
True
False
False

如果要查看是否单调递减,则使用is_monotonic_decreasing:

print(df['A'].is_monotonic_decreasing)
print(df['B'].is_monotonic_decreasing)
print(df['C'].is_monotonic_decreasing)
False
False
True

如果我们要按行查看我们需要结合apply函数,并使用is_monotonic.fget()

df.apply(pd.Series.is_monotonic.fget, axis=1)
0     True
1 True
2 False
3 False
4 False
dtype: bool

查看单调递减也是类似的:

df.apply(pd.Series.is_monotonic_decreasing.fget, axis=1)
0    False
1 False
2 False
3 True
4 True
dtype: bool

3.16 一次性合并多个DataFrame

df1 = pd.DataFrame({'A':['a', 'b', 'c'], 'm1':[1, 2, 3]})
df2 = pd.DataFrame({'A':['b', 'c', 'd'], 'm2':[3, 4, 3]})
df3 = pd.DataFrame({'A':['a', 'c', 'f'], 'm3':[4, 2, 1]})

假设我们想将这三个DataFrame合并,查看A列中每个不同的元素从m1到m3期间的变化(outer merge)。如果我们使用两两合并,当DataFrame的数量很多时,我们就需要写很多行代码,在此可以考虑采用reduce:

from functools import reduce
df_ls = [df1, df2, df3] # 将需要合并的DataFrame放入一个列表中
reduce(lambda left, right: pd.merge(left, right, how='outer', on='A'), df_ls) # 第一个参数为需要执行的操作,第二个参数为列表

A m1 m2 m3
0 a 1.0 NaN 4.0
1 b 2.0 3.0 NaN
2 c 3.0 4.0 2.0
3 d NaN 3.0 NaN
4 f NaN NaN 1.0

3.17 共现矩阵

df = pd.DataFrame({'labal_A':[1, 0, 0, 1, 1, 0, 1, 0],
                   'labal_B':[1, 1, 0, 1, 0, 0, 1, 0], 
                   'labal_C':[0, 1, 1, 1, 0, 0, 1, 1],
                   'labal_D':[0, 0, 1, 0, 1, 1, 1, 1]})
df

labal_A labal_B labal_C labal_D
0 1 1 0 0
1 0 1 1 0
2 0 0 1 1
3 1 1 1 0
4 1 0 0 1
5 0 0 0 1
6 1 1 1 1
7 0 0 1 1

假设我们有一组0-1标签(label_A到label_D),我们想要获得标签之间的共现矩阵,即标签两两之间共同出现的次数。我们可以使用转置后点积的方法:

df.T.dot(df)

labal_A labal_B labal_C labal_D
labal_A 4 3 2 2
labal_B 3 4 3 1
labal_C 2 3 5 3
labal_D 2 1 3 5

其中对角线上的数代表每个标签中1出现了多少次,该矩阵是一个对称矩阵。

3.18 笛卡尔积

a = ['id_1', 'id_2', 'id_3', 'id_4']
b = pd.date_range('2020-01-01', '2020-01-03')
c = ['type_A', 'type_B']

假设我们有上述数据,并希望生成它们的笛卡尔积(即a, b, c三者所有可能的排列组合)。我们可以先用它们建立一个MultiIndex,然后装入一个空的DataFrame中并reset_index即可:

idx = pd.MultiIndex.from_product([a, b, c], names=['uid', 'date', 'type'])
pd.DataFrame(index=idx).reset_index()

uid date type
0 id_1 2020-01-01 type_A
1 id_1 2020-01-01 type_B
2 id_1 2020-01-02 type_A
3 id_1 2020-01-02 type_B
4 id_1 2020-01-03 type_A
5 id_1 2020-01-03 type_B
6 id_2 2020-01-01 type_A
7 id_2 2020-01-01 type_B
8 id_2 2020-01-02 type_A
9 id_2 2020-01-02 type_B
10 id_2 2020-01-03 type_A
11 id_2 2020-01-03 type_B
12 id_3 2020-01-01 type_A
13 id_3 2020-01-01 type_B
14 id_3 2020-01-02 type_A
15 id_3 2020-01-02 type_B
16 id_3 2020-01-03 type_A
17 id_3 2020-01-03 type_B
18 id_4 2020-01-01 type_A
19 id_4 2020-01-01 type_B
20 id_4 2020-01-02 type_A
21 id_4 2020-01-02 type_B
22 id_4 2020-01-03 type_A
23 id_4 2020-01-03 type_B

3.19 行转列

df = pd.DataFrame({'product':['A', 'B', 'C'], 
                   '2020-01-01':[5, 1, 3], 
                   '2020-01-02':[2, 2, 3], 
                   '2020-01-03':[4, 3, 6]})
df

product 2020-01-01 2020-01-02 2020-01-03
0 A 5 2 4
1 B 1 2 3
2 C 3 3 6

假设我们有一些销售数据,每一天的记录都是单独的一列,而我们想将其转化成所有日期都在同一列的样子,我们可以使用行转列的melt函数:

pd.melt(df, id_vars='product')

product variable value
0 A 2020-01-01 5
1 B 2020-01-01 1
2 C 2020-01-01 3
3 A 2020-01-02 2
4 B 2020-01-02 2
5 C 2020-01-02 3
6 A 2020-01-03 4
7 B 2020-01-03 3
8 C 2020-01-03 6

P4 时间相关

4.1 创建时间信息

df = pd.DataFrame()
df

df['date'] = pd.date_range('2019-01-01', '2019-01-06') # 按日,通过起止时间
df

date
0 2019-01-01
1 2019-01-02
2 2019-01-03
3 2019-01-04
4 2019-01-05
5 2019-01-06

另一种方法是通过开始时间和时长,如下:

df = pd.DataFrame()
df['date'] = pd.date_range('2019-01-01', periods=10) # 按日,从2019年1月1日起,时长为10天
df

date
0 2019-01-01
1 2019-01-02
2 2019-01-03
3 2019-01-04
4 2019-01-05
5 2019-01-06
6 2019-01-07
7 2019-01-08
8 2019-01-09
9 2019-01-10
df['date'].values[0]
numpy.datetime64('2019-01-01T00:00:00.000000000')
type(df['date'].values[0])
numpy.datetime64

如果我们希望指定更小的时间粒度,我们需要:
(1)细化起止时间;
(2)修改freq参数。

df = pd.DataFrame()
df['date'] = pd.date_range('2019-01-01 01:40:00', '2019-01-01 10:00:00', freq='1h') # 按小时
df

date
0 2019-01-01 01:40:00
1 2019-01-01 02:40:00
2 2019-01-01 03:40:00
3 2019-01-01 04:40:00
4 2019-01-01 05:40:00
5 2019-01-01 06:40:00
6 2019-01-01 07:40:00
7 2019-01-01 08:40:00
8 2019-01-01 09:40:00

4.2 文本转化为时间

df = pd.DataFrame({'date': ['01/05/2016', '01/28/2017', '02/21/2018', '03/05/2018', '11/24/2019']})
df

date
0 01/05/2016
1 01/28/2017
2 02/21/2018
3 03/05/2018
4 11/24/2019

我们经常会在表格中遇到各种代表时间的文本,有时为了后续操作方便,我们需要将其转化为python可以识别的时间信息(比如Timestamp),我们可以使用pd.to_datetime:

df['date'] = pd.to_datetime(df['date'], format='%m/%d/%Y')
df

date
0 2016-01-05
1 2017-01-28
2 2018-02-21
3 2018-03-05
4 2019-11-24

我们可以在format参数中指定原始数据的格式,以便正确解析(有时不规定format出来的结果可能是错的)。另外需要注意的是,有些年份只用两位数,如17代表2017年,这时在format中要使用%y作为占位符,而不是%Y,否则会报错。

df = pd.DataFrame({'date': ['01/05/16', '01/28/17', '02/21/18', '03/05/18', '11/24/19']})
df

date
0 01/05/16
1 01/28/17
2 02/21/18
3 03/05/18
4 11/24/19
df['date'] = pd.to_datetime(df['date'], format='%m/%d/%y') # 注意y小写
df

date
0 2016-01-05
1 2017-01-28
2 2018-02-21
3 2018-03-05
4 2019-11-24

4.3 间隔时间选取

df = pd.DataFrame({'date': pd.date_range('2018-12-01', '2018-12-20')})
df

date
0 2018-12-01
1 2018-12-02
2 2018-12-03
3 2018-12-04
4 2018-12-05
5 2018-12-06
6 2018-12-07
7 2018-12-08
8 2018-12-09
9 2018-12-10
10 2018-12-11
11 2018-12-12
12 2018-12-13
13 2018-12-14
14 2018-12-15
15 2018-12-16
16 2018-12-17
17 2018-12-18
18 2018-12-19
19 2018-12-20

假设我们希望每隔3天取一个值(即1、4、7……),只需利用索引即可。

df[df.index%3 == 0] # 后续可以根据需求决定是否要reset_index

date
0 2018-12-01
3 2018-12-04
6 2018-12-07
9 2018-12-10
12 2018-12-13
15 2018-12-16
18 2018-12-19

如果我们的时间是不连续的话,无法利用索引,该怎么办呢?

df = pd.DataFrame({'date': list(pd.date_range('2018-12-01', '2018-12-10')) + list(pd.date_range('2018-12-15', '2018-12-20'))})
df # 12-11 到 12-14 是缺失的

date
0 2018-12-01
1 2018-12-02
2 2018-12-03
3 2018-12-04
4 2018-12-05
5 2018-12-06
6 2018-12-07
7 2018-12-08
8 2018-12-09
9 2018-12-10
10 2018-12-15
11 2018-12-16
12 2018-12-17
13 2018-12-18
14 2018-12-19
15 2018-12-20
df[df['date'].isin(pd.date_range('2018-12-01', '2018-12-20', freq='3D'))]

date
0 2018-12-01
3 2018-12-04
6 2018-12-07
9 2018-12-10
11 2018-12-16
14 2018-12-19

逐段分解上述命令:
(1)首先,最里面的括号,我们创建了一个日期索引,首尾与df中的日期对齐,间隔为3天;
(2)然后我们选取df的date列中存在于上述日期索引的行。

4.3 获取某个日期是星期几

df = pd.DataFrame()
df['date'] = pd.date_range('2019-01-01', periods=10)
df

date
0 2019-01-01
1 2019-01-02
2 2019-01-03
3 2019-01-04
4 2019-01-05
5 2019-01-06
6 2019-01-07
7 2019-01-08
8 2019-01-09
9 2019-01-10

假设我们需要知道当前每个日期是星期几,我们需要导入datetime,然后使用apply来完成。

from datetime import datetime
df['weekday'] = df['date'].apply(datetime.weekday) 
df

date weekday
0 2019-01-01 1
1 2019-01-02 2
2 2019-01-03 3
3 2019-01-04 4
4 2019-01-05 5
5 2019-01-06 6
6 2019-01-07 0
7 2019-01-08 1
8 2019-01-09 2
9 2019-01-10 3

注意:这里的weekday是0-6,其中0代表星期一。







源码:

https://www.kesci.com/mw/project/604a285c74dfc60016e0fda0

https://github.com/SilenceGTX/pandas_tricks






欢迎大家加入QQ学习交流群~

‍‍‍‍‍‍‍‍‍

‍‍‍‍‍‍‍‍‍添加下面微信可加入微信学习交流群,备注“CH”



扫描二维码获取

更多精彩

ChallengeHub



点个

在看

你最好看



原文作者:ChallengeHub;如有侵权,请联系管理员删除。

请登录后发表评论 登录
全部评论

注册时间:2019-01-07

  • 博文量
    350
  • 访问量
    160978