Pandas非常用技巧汇总

2023-08-25 13:03:57 浏览数 (1)

Pandas非常用技巧汇总

原创致GreatChallengeHub

代码语言:javascript复制
import pandas as pd
import numpy as np
import re

P1 缺失值填充

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

代码语言:javascript复制
df = pd.DataFrame({'A': [1, 2, 3, 4, 5], 'B': [1, np.nan, 3, np.nan, 5]})
代码语言:javascript复制
df

A

B

0

1

1.0

1

2

NaN

2

3

3.0

3

4

NaN

4

5

5.0

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

代码语言:javascript复制
df['B'] = df['B'].fillna(df['A']) # 简单地使用fillna就可以
代码语言:javascript复制
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 用本列的均值来填充本列缺失值

代码语言:javascript复制
df = pd.DataFrame({'A': [1, np.nan, 3, np.nan, 5]})
代码语言:javascript复制
df

A

0

1.0

1

NaN

2

3.0

3

NaN

4

5.0

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

代码语言:javascript复制
df['A'].fillna(df['A'].mean(), inplace=True)
代码语言:javascript复制
df

A

0

1.0

1

3.0

2

3.0

3

3.0

4

5.0

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

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

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

代码语言:javascript复制
df = pd.DataFrame({'A': ['a', 'b', 'a', 'b', 'a', 'b'], 'B': [1, np.nan, 3, 3, np.nan, 5]})
代码语言:javascript复制
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)。

代码语言:javascript复制
df['B'] = df.groupby('A')['B'].transform(lambda x: x.fillna(x.mean()))
代码语言:javascript复制
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 行列缺失情况统计

代码语言:javascript复制
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]})
代码语言:javascript复制
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的数量。

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

P2 groupby相关

2.1 保留聚合列

代码语言:javascript复制
df = pd.DataFrame({'A': ['a', 'b', 'a', 'b', 'a', 'b'], 'B': [1, 2, 3, 3, 4, 5]})
代码语言:javascript复制
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列的内容被加入了索引中。

代码语言:javascript复制
df.groupby('A')['B'].sum()
代码语言:javascript复制
A
a     8
b    10
Name: B, dtype: int64

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

代码语言:javascript复制
df.groupby('A', as_index=False)['B'].sum()

A

B

0

a

8

1

b

10

2.2 取前n项

代码语言:javascript复制
df = pd.DataFrame({'A': ['a', 'a', 'a', 'a', 'b', 'b', 'b'], 'B': [1, 3, 2, 4, 5, 2, 3]})
代码语言:javascript复制
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:

代码语言:javascript复制
df.groupby('A')['B'].head(2)
代码语言:javascript复制
0    1
1    3
4    5
5    2
Name: B, dtype: int64

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

代码语言:javascript复制
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来选取:

代码语言:javascript复制
df

A

B

0

a

1

1

a

3

2

a

2

3

a

4

4

b

5

5

b

2

6

b

3

代码语言:javascript复制
df.sort_values(['A', 'B'], ascending=False, inplace=True) # 第1步
代码语言:javascript复制
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项。

代码语言:javascript复制
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项

代码语言:javascript复制
df = pd.DataFrame({'A': ['a', 'a', 'a', 'a', 'b', 'b', 'b'], 'B': [1, 3, 2, 4, 5, 2, 3]})
代码语言:javascript复制
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:

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

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

代码语言:javascript复制
df.groupby('A', as_index=False).apply(lambda x: x.iloc[1]) 

A

B

0

a

3

1

b

2

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

代码语言:javascript复制
df[df.groupby('A').cumcount() == 3] # 同样计数从0开始,此处即取第4项

A

B

3

a

4

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

代码语言:javascript复制
df

A

B

0

a

1

1

a

3

2

a

2

3

a

4

4

b

5

5

b

2

6

b

3

代码语言:javascript复制
df.sort_values(['A', 'B'], ascending=False, inplace=True) # 第1步
代码语言:javascript复制
df[df.groupby('A').cumcount() == 1] # 第2步

A

B

6

b

3

1

a

3

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

代码语言:javascript复制
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]
代码语言:javascript复制
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:

代码语言:javascript复制
df.groupby([pd.Grouper(key='date', freq='7d'), 'shop'])['sales'].sum()
代码语言:javascript复制
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即可:

代码语言:javascript复制
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 对不同的列进行不同类型的聚合

代码语言:javascript复制
df=pd.DataFrame({'A':['g1', 'g1', 'g2', 'g2', 'g1'],'B':[3, 2, 3, 4, 2],'C':[1, 2, 1, 3, 4]})
代码语言:javascript复制
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函数完成操作。

代码语言:javascript复制
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 分组后装入列表

代码语言:javascript复制
df=pd.DataFrame({'A':['g1', 'g1', 'g2', 'g2', 'g1'],'B':[3, 2, 3, 4, 2]})
代码语言:javascript复制
df

A

B

0

g1

3

1

g1

2

2

g2

3

3

g2

4

4

g1

2

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

代码语言:javascript复制
df.groupby('A')['B'].apply(list).reset_index()

A

B

0

g1

[3, 2, 2]

1

g2

[3, 4]

2.7 列表元素拆出

代码语言:javascript复制
df=pd.DataFrame({'A':['g1', 'g2'],'B':[[3, 2, 1], [4, 2]]})
代码语言:javascript复制
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)。

代码语言:javascript复制
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为列合并(行数不变)
代码语言:javascript复制
df2

A

B

0

g1

3

1

g1

2

2

g1

1

3

g2

4

4

g2

2

2.8 组内打乱

代码语言:javascript复制
df = pd.DataFrame({'A':['g1', 'g1', 'g1', 'g1', 'g2', 'g2', 'g2'],'B':[1, 2, 3, 4, 5, 6, 7]})
代码语言:javascript复制
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来完成:

代码语言:javascript复制
df['B'] = df.groupby('A')['B'].transform(np.random.permutation)
代码语言:javascript复制
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 组内出现次数相关

代码语言:javascript复制
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]})
代码语言:javascript复制
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列每个元素出现的次数:

代码语言:javascript复制
df.groupby('A')['B'].value_counts()
代码语言:javascript复制
A  B
a  3    2
   1    1
   2    1
   4    1
b  2    4
   3    1
   5    1
Name: B, dtype: int64

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

代码语言:javascript复制
df.groupby('A')['B'].apply(lambda x: x.value_counts().idxmax())
代码语言:javascript复制
A
a    3
b    2
Name: B, dtype: int64

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

代码语言:javascript复制
df.groupby('A')['B'].apply(lambda x: x.value_counts().max())
代码语言:javascript复制
A
a    2
b    4
Name: B, dtype: int64

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

代码语言:javascript复制

P3 行列操作

3.1 复制多行

代码语言:javascript复制
df = pd.DataFrame({'A': ['a', 'b'], 'B': [1, 3]})
代码语言:javascript复制
df

A

B

0

a

1

1

b

3

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

代码语言:javascript复制
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:

代码语言:javascript复制
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 合并多列内容(文本)

代码语言:javascript复制
df = pd.DataFrame({'A': [1988, 1993], 'B': ['02', '03'], 'C':[21, 13]})
代码语言:javascript复制
df

A

B

C

0

1988

02

21

1

1993

03

13

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

代码语言:javascript复制
df['D'] = df['A'].astype(str)   '-'   df['B'].astype(str)   '-'   df['C'].astype(str)
代码语言:javascript复制
df

A

B

C

D

0

1988

02

21

1988-02-21

1

1993

03

13

1993-03-13

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

3.3 拆分列内容(文本)

代码语言:javascript复制
df = pd.DataFrame({'A': ['1988-02-21', '1993-03-13']})
代码语言:javascript复制
df

A

0

1988-02-21

1

1993-03-13

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

代码语言:javascript复制
df2 = pd.DataFrame(df['A'].str.split('-').tolist(), columns=['year', 'month', 'day'])
代码语言:javascript复制
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 拆分列内容(文本,拆分后不定长)

代码语言:javascript复制
df = pd.DataFrame({'A': ['88-02-21-00-23', '93-03-13', '00-51-03-13']})
代码语言:javascript复制
df

A

0

88-02-21-00-23

1

93-03-13

2

00-51-03-13

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

代码语言:javascript复制
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 选取包含特定文本的列

代码语言:javascript复制
df = pd.DataFrame({'A': ['highest', 'good', 'just', 'newest', 'newer', 'estimate']})
代码语言:javascript复制
df

A

0

highest

1

good

2

just

3

newest

4

newer

5

estimate

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

代码语言:javascript复制
df[df['A'].str.contains('est')]

A

0

highest

3

newest

5

estimate

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

代码语言:javascript复制
df = pd.DataFrame({'A': ['yes ok', 'yesok', 'yes 2 ok', 'okok', 'yes yes', 'ok yes']})
代码语言:javascript复制
df

A

0

yes ok

1

yesok

2

yes 2 ok

3

okok

4

yes yes

5

ok yes

代码语言:javascript复制
df[df['A'].str.contains(re.compile('yes. ok'))]

A

0

yes ok

2

yes 2 ok

3.6 clip的用途

代码语言:javascript复制
df = pd.DataFrame({'Name': ['Apple', 'Peach', 'Melon', 'Grape', 'Banana'], 'Sales': [2, 0, 5, 1, -1]})
代码语言:javascript复制
df

Name

Sales

0

Apple

2

1

Peach

0

2

Melon

5

3

Grape

1

4

Banana

-1

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

代码语言:javascript复制
df['label'] = df['Sales'].clip(0, 2) # 下限为0,上限为2
代码语言:javascript复制
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 值替换

代码语言:javascript复制
df = pd.DataFrame({'A': ['a', 'b', 'a', 'b', 'a', 'b'], 'B': [1, 2, 3, 3, 3, 5]})
代码语言:javascript复制
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命令来进行。

代码语言:javascript复制
dic = {'a': 'Male', 'b': 'Female'}
代码语言:javascript复制
df['A'] = df['A'].map(dic)
代码语言:javascript复制
df

A

B

0

Male

1

1

Female

2

2

Male

3

3

Female

3

4

Male

3

5

Female

5

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

代码语言:javascript复制
df = pd.DataFrame({'A': ['a', 'b', 'a', 'b', 'a', 'b', 'c', 'd'], 'B': [1, 2, 3, 3, 3, 5, 4, 5]})
代码语言:javascript复制
df['A'] = df['A'].map(dic)
代码语言:javascript复制
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()函数的结合。

代码语言:javascript复制
df = pd.DataFrame({'A': ['a', 'b', 'a', 'b', 'a', 'b', 'c', 'd'], 'B': [1, 2, 3, 3, 3, 5, 4, 5]})
代码语言:javascript复制
df['A'] = df['A'].replace(dic)
代码语言:javascript复制
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 最值查询

代码语言:javascript复制
df = pd.DataFrame({'Name': ['Apple', 'Peach', 'Melon', 'Grape', 'Banana'], 'Sales': [2, 0, 5, 1, -1]})
代码语言:javascript复制
df

Name

Sales

0

Apple

2

1

Peach

0

2

Melon

5

3

Grape

1

4

Banana

-1

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

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

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

代码语言:javascript复制
df['Name'].iloc[df['Sales'].idxmax()]
代码语言:javascript复制
'Melon'

3.9 选出第N大/第N小

代码语言:javascript复制
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]})
代码语言:javascript复制
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:

代码语言:javascript复制
df[df['A'].rank(method='dense', ascending=False) == 2]

A

B

8

7

42

9

7

1

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

如果要选择第三小的值:

代码语言:javascript复制
df[df['A'].rank(method='dense', ascending=True) == 3]

A

B

4

3

4

3.10 选取特定数据类型的行

代码语言:javascript复制
df = pd.DataFrame({'A': [1988, 1993, 'noise', 'noise2', 2018, 'noise3']})
代码语言:javascript复制
df

A

0

1988

1

1993

2

noise

3

noise2

4

2018

5

noise3

代码语言:javascript复制
df.info()
代码语言:javascript复制
<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
代码语言:javascript复制
df

A

0

1988

1

1993

2

noise

3

noise2

4

2018

5

noise3

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

代码语言:javascript复制
df[type(df['A']) == int]
代码语言:javascript复制
---------------------------------------------------------------------------

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来完成选取。

代码语言:javascript复制
df[df['A'].apply(lambda x: type(x) == int)]

A

0

1988

1

1993

4

2018

3.11 总体统计

代码语言:javascript复制
df = pd.DataFrame({'A':[1, 2, 1, 3], 'B':[2, 1, 1, 4], 'C':[3, 4, 1, 1]})
代码语言:javascript复制
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函数:

代码语言:javascript复制
pd.Series(np.ravel(df)).value_counts()
代码语言:javascript复制
1    6
4    2
3    2
2    2
dtype: int64

3.12 滑窗统计相关

代码语言:javascript复制
df = pd.DataFrame({'signal': [1, 2, 3, 2, 5, 3, 5, 6, 3, 2, 3, 5, 6, 2, 1, 3, 2]})
代码语言:javascript复制
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的和:

代码语言:javascript复制
df['rolling_top3_sum'] = df['signal'].rolling(5).apply(lambda x: np.sum(np.sort(x)[-3:]))
代码语言:javascript复制
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 导出字典

代码语言:javascript复制
df = pd.DataFrame({'A':['a', 'b', 'c', 'd'], 'B':[2, 1, 1, 4]})
代码语言:javascript复制
df

A

B

0

a

2

1

b

1

2

c

1

3

d

4

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

代码语言:javascript复制
df.set_index('A').to_dict()
代码语言:javascript复制
{'B': {'a': 2, 'b': 1, 'c': 1, 'd': 4}}

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

代码语言:javascript复制
df.set_index('A').to_dict()['B']
代码语言:javascript复制
{'a': 2, 'b': 1, 'c': 1, 'd': 4}

3.14 Count Encoding

代码语言:javascript复制
df = pd.DataFrame({'A':[1, 1, 1, 2, 3, 1, 3, np.nan], 'B':[1, 1, 1, 1, 1, 2, 2, 2]})
代码语言:javascript复制
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函数:

代码语言:javascript复制
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 检查单调性

代码语言:javascript复制
df = pd.DataFrame({'A':[1, 1, 2, 3, 5], 'B':[2, 1, 1, 2, 4], 'C':[5, 4, 3, 2, 1]})
代码语言:javascript复制
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的属性:

代码语言:javascript复制
print(df['A'].is_monotonic)
print(df['B'].is_monotonic)
print(df['C'].is_monotonic)
代码语言:javascript复制
True
False
False

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

代码语言:javascript复制
print(df['A'].is_monotonic_decreasing)
print(df['B'].is_monotonic_decreasing)
print(df['C'].is_monotonic_decreasing)
代码语言:javascript复制
False
False
True

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

代码语言:javascript复制
df.apply(pd.Series.is_monotonic.fget, axis=1)
代码语言:javascript复制
0     True
1     True
2    False
3    False
4    False
dtype: bool

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

代码语言:javascript复制
df.apply(pd.Series.is_monotonic_decreasing.fget, axis=1)
代码语言:javascript复制
0    False
1    False
2    False
3     True
4     True
dtype: bool

3.16 一次性合并多个DataFrame

代码语言:javascript复制
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:

代码语言:javascript复制
from functools import reduce
代码语言:javascript复制
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 共现矩阵

代码语言:javascript复制
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]})
代码语言:javascript复制
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),我们想要获得标签之间的共现矩阵,即标签两两之间共同出现的次数。我们可以使用转置后点积的方法:

代码语言:javascript复制
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 笛卡尔积

代码语言:javascript复制
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即可:

代码语言:javascript复制
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 行转列

代码语言:javascript复制
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]})
代码语言:javascript复制
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函数:

代码语言:javascript复制
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

代码语言:javascript复制

P4 时间相关

4.1 创建时间信息

代码语言:javascript复制
df = pd.DataFrame()
代码语言:javascript复制
df
代码语言:javascript复制
df['date'] = pd.date_range('2019-01-01', '2019-01-06') # 按日,通过起止时间
代码语言:javascript复制
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

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

代码语言:javascript复制
df = pd.DataFrame()
df['date'] = pd.date_range('2019-01-01', periods=10) # 按日,从2019年1月1日起,时长为10天
代码语言:javascript复制
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

代码语言:javascript复制
df['date'].values[0]
代码语言:javascript复制
numpy.datetime64('2019-01-01T00:00:00.000000000')
代码语言:javascript复制
type(df['date'].values[0])
代码语言:javascript复制
numpy.datetime64

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

代码语言:javascript复制
df = pd.DataFrame()
代码语言:javascript复制
df['date'] = pd.date_range('2019-01-01 01:40:00', '2019-01-01 10:00:00', freq='1h') # 按小时
代码语言:javascript复制
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 文本转化为时间

代码语言:javascript复制
df = pd.DataFrame({'date': ['01/05/2016', '01/28/2017', '02/21/2018', '03/05/2018', '11/24/2019']})
代码语言:javascript复制
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:

代码语言:javascript复制
df['date'] = pd.to_datetime(df['date'], format='%m/%d/%Y')
代码语言:javascript复制
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,否则会报错。

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

date

0

01/05/16

1

01/28/17

2

02/21/18

3

03/05/18

4

11/24/19

代码语言:javascript复制
df['date'] = pd.to_datetime(df['date'], format='%m/%d/%y') # 注意y小写
代码语言:javascript复制
df

date

0

2016-01-05

1

2017-01-28

2

2018-02-21

3

2018-03-05

4

2019-11-24

4.3 间隔时间选取

代码语言:javascript复制
df = pd.DataFrame({'date': pd.date_range('2018-12-01', '2018-12-20')})
代码语言:javascript复制
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……),只需利用索引即可。

代码语言:javascript复制
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

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

代码语言:javascript复制
df = pd.DataFrame({'date': list(pd.date_range('2018-12-01', '2018-12-10'))   list(pd.date_range('2018-12-15', '2018-12-20'))})
代码语言:javascript复制
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

代码语言:javascript复制
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 获取某个日期是星期几

代码语言:javascript复制
df = pd.DataFrame()
df['date'] = pd.date_range('2019-01-01', periods=10)
代码语言:javascript复制
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来完成。

代码语言:javascript复制
from datetime import datetime
df['weekday'] = df['date'].apply(datetime.weekday) 
代码语言:javascript复制
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

0 人点赞