Pandas非常用技巧汇总

Pandas非常用技巧汇总

原创致GreatChallengeHub

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

AB011.012NaN233.034NaN455.0

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

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

AB011.0122.0233.0344.0455.0

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

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

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

A01.01NaN23.03NaN45.0

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

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

A01.013.023.033.045.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

AB0a1.01bNaN2a3.03b3.04aNaN5b5.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

AB0a1.01b4.02a3.03b3.04a2.05b5.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

ABC01.01.01.01NaNNaNNaN23.0NaN2.033.03.03.04NaNNaN4.055.05.05.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

AB0a11b22a33b34a45b5

我们按照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()

AB0a81b10

2.2 取前n项

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

AB0a11a32a23a44b55b26b3

假设我们想按照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,否则返回多重索引

AB0a11a34b55b2

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

df

AB0a11a32a23a44b55b26b3

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

AB3a41a34b56b3

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

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

AB3a41a32a20a14b56b35b2

2.3 取第n项

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

AB0a11a32a23a44b55b26b3

假设我们想按照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]) 

AB0a31b2

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

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

AB3a4

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

df

AB0a11a32a23a44b55b26b3

df.sort_values(['A', 'B'], ascending=False, inplace=True) # 第1步
df[df.groupby('A').cumcount() == 1] # 第2步

AB6b31a3

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

dateshopsales02018-01-01shop_A112018-01-02shop_A222018-01-03shop_A532018-01-04shop_A342018-01-05shop_A152018-01-06shop_A562018-01-07shop_A272018-01-08shop_A382018-01-09shop_A192018-01-10shop_A1102018-01-11shop_A0112018-01-12shop_A0122018-01-13shop_A4132018-01-14shop_A5142018-01-01shop_B5152018-01-02shop_B1162018-01-03shop_B1172018-01-04shop_B2182018-01-05shop_B3192018-01-06shop_B4202018-01-07shop_B4212018-01-08shop_B0222018-01-09shop_B1232018-01-10shop_B2242018-01-11shop_B1252018-01-12shop_B2262018-01-13shop_B0272018-01-14shop_B5

通常情况下,如果我们想按照时间来聚合(从日到周),我们可以使用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()

dateshopsales02018-01-01shop_A1912018-01-01shop_B2022018-01-08shop_A1432018-01-08shop_B11

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

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

ABC0g1311g1222g2313g2434g124

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

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

ABC0g172.3333331g272.000000

2.6 分组后装入列表

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

AB0g131g122g233g244g12

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

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

AB0g1[3, 2, 2]1g2[3, 4]

2.7 列表元素拆出

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

AB0g1[3, 2, 1]1g2[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

AB0g131g122g113g244g22

2.8 组内打乱

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

AB0g111g122g133g144g255g266g27

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

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

AB0g111g122g143g134g275g266g25

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

AB0a11a32a23a44a35b56b27b38b29b210b2

假设我们想知道根据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

AB0a11b3

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

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

AB0a11b30a11b30a11b30a11b3

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

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

AB0a11a12a13a14b35b36b37b3

3.2 合并多列内容(文本)

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

ABC019880221119930313

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

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

ABCD0198802211988-02-211199303131993-03-13

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

3.3 拆分列内容(文本)

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

A01988-02-2111993-03-13

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

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

yearmonthday019880221119930313

逐段分解上述命令:
(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

A088-02-21-00-23193-03-13200-51-03-13

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

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

01234088022100231930313NoneNone200510313None

3.5 选取包含特定文本的列

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

A0highest1good2just3newest4newer5estimate

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

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

A0highest3newest5estimate

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

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

A0yes ok1yesok2yes 2 ok3okok4yes yes5ok yes

df[df['A'].str.contains(re.compile('yes.+ok'))]

A0yes ok2yes 2 ok

3.6 clip的用途

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

NameSales0Apple21Peach02Melon53Grape14Banana-1

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

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

NameSaleslabel0Apple221Peach002Melon523Grape114Banana-10

3.7 值替换

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

AB0a11b22a33b34a35b5

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

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

AB0Male11Female22Male33Female34Male35Female5

然而,使用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

AB0Male11Female22Male33Female34Male35Female56NaN47NaN5

可以看到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

AB0Male11Female22Male33Female34Male35Female56c47d5

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

3.8 最值查询

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

NameSales0Apple21Peach02Melon53Grape14Banana-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

AB01211422232143454565117623874297110822

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

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

AB8742971

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

如果要选择第三小的值:

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

AB434

3.10 选取特定数据类型的行

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

A01988119932noise3noise2420185noise3

df.info()
<class 'pandas.core.frame.dataframe'>
RangeIndex: 6 entries, 0 to 5
Data columns (total 1 columns):
 #   Column  Non-Null Count  Dtype

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&#xA0;x:&#xA0;type(x)&#xA0;==&#xA0;int)]

A019881199342018

3.11 总体统计

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

ABC0123121421113341

我们知道通常我们对某一列进行数值统计的时候会用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&#xA0;=&#xA0;pd.DataFrame({'signal':&#xA0;[1,&#xA0;2,&#xA0;3,&#xA0;2,&#xA0;5,&#xA0;3,&#xA0;5,&#xA0;6,&#xA0;3,&#xA0;2,&#xA0;3,&#xA0;5,&#xA0;6,&#xA0;2,&#xA0;1,&#xA0;3,&#xA0;2]})
df

signal01122332455365768392103115126132141153162

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

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

signalrolling_top3_sum01NaN12NaN23NaN32NaN4510.05311.06513.07616.08316.09214.010314.011514.012614.013214.014114.015314.016211.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&#xA0;=&#xA0;pd.DataFrame({'A':['a',&#xA0;'b',&#xA0;'c',&#xA0;'d'],&#xA0;'B':[2,&#xA0;1,&#xA0;1,&#xA0;4]})
df

AB0a21b12c13d4

我们假设我们希望导出一个字典,以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&#xA0;=&#xA0;pd.DataFrame({'A':[1,&#xA0;1,&#xA0;1,&#xA0;2,&#xA0;3,&#xA0;1,&#xA0;3,&#xA0;np.nan],&#xA0;'B':[1,&#xA0;1,&#xA0;1,&#xA0;1,&#xA0;1,&#xA0;2,&#xA0;2,&#xA0;2]})
df

AB01.0111.0121.0132.0143.0151.0263.027NaN2

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

tmp&#xA0;=&#xA0;df['A'].value_counts().to_dict()
df['A_cnt']&#xA0;=&#xA0;df['A'].map(tmp)
df

ABA_cnt01.014.011.014.021.014.032.011.043.012.051.024.063.022.07NaN2NaN

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

3.15 检查单调性

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

ABC01251114221333224541

假设我们希望检查某一列是否单调递增,我们可以使用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,&#xA0;axis=1)
0     True
1     True
2    False
3    False
4    False
dtype: bool

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

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

3.16 一次性合并多个DataFrame

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

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

from&#xA0;functools&#xA0;import&#xA0;reduce
df_ls&#xA0;=&#xA0;[df1,&#xA0;df2,&#xA0;df3]&#xA0;#&#xA0;&#x5C06;&#x9700;&#x8981;&#x5408;&#x5E76;&#x7684;DataFrame&#x653E;&#x5165;&#x4E00;&#x4E2A;&#x5217;&#x8868;&#x4E2D;
reduce(lambda&#xA0;left,&#xA0;right:&#xA0;pd.merge(left,&#xA0;right,&#xA0;how='outer',&#xA0;on='A'),&#xA0;df_ls)&#xA0;#&#xA0;&#x7B2C;&#x4E00;&#x4E2A;&#x53C2;&#x6570;&#x4E3A;&#x9700;&#x8981;&#x6267;&#x884C;&#x7684;&#x64CD;&#x4F5C;&#xFF0C;&#x7B2C;&#x4E8C;&#x4E2A;&#x53C2;&#x6570;&#x4E3A;&#x5217;&#x8868;

Am1m2m30a1.0NaN4.01b2.03.0NaN2c3.04.02.03dNaN3.0NaN4fNaNNaN1.0

3.17 共现矩阵

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

labal_Alabal_Blabal_Clabal_D0110010110200113111041001500016111170011

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

df.T.dot(df)

labal_Alabal_Blabal_Clabal_Dlabal_A4322labal_B3431labal_C2353labal_D2135

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

3.18 笛卡尔积

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

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

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

uiddatetype0id_12020-01-01type_A1id_12020-01-01type_B2id_12020-01-02type_A3id_12020-01-02type_B4id_12020-01-03type_A5id_12020-01-03type_B6id_22020-01-01type_A7id_22020-01-01type_B8id_22020-01-02type_A9id_22020-01-02type_B10id_22020-01-03type_A11id_22020-01-03type_B12id_32020-01-01type_A13id_32020-01-01type_B14id_32020-01-02type_A15id_32020-01-02type_B16id_32020-01-03type_A17id_32020-01-03type_B18id_42020-01-01type_A19id_42020-01-01type_B20id_42020-01-02type_A21id_42020-01-02type_B22id_42020-01-03type_A23id_42020-01-03type_B

3.19 行转列

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

product2020-01-012020-01-022020-01-030A5241B1232C336

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

pd.melt(df,&#xA0;id_vars='product')

productvariablevalue0A2020-01-0151B2020-01-0112C2020-01-0133A2020-01-0224B2020-01-0225C2020-01-0236A2020-01-0347B2020-01-0338C2020-01-036


P4 时间相关

4.1 创建时间信息

df&#xA0;=&#xA0;pd.DataFrame()
df
df['date']&#xA0;=&#xA0;pd.date_range('2019-01-01',&#xA0;'2019-01-06')&#xA0;#&#xA0;&#x6309;&#x65E5;&#xFF0C;&#x901A;&#x8FC7;&#x8D77;&#x6B62;&#x65F6;&#x95F4;
df

date02019-01-0112019-01-0222019-01-0332019-01-0442019-01-0552019-01-06

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

df&#xA0;=&#xA0;pd.DataFrame()
df['date']&#xA0;=&#xA0;pd.date_range('2019-01-01',&#xA0;periods=10)&#xA0;#&#xA0;&#x6309;&#x65E5;&#xFF0C;&#x4ECE;2019&#x5E74;1&#x6708;1&#x65E5;&#x8D77;&#xFF0C;&#x65F6;&#x957F;&#x4E3A;10&#x5929;
df

date02019-01-0112019-01-0222019-01-0332019-01-0442019-01-0552019-01-0662019-01-0772019-01-0882019-01-0992019-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&#xA0;=&#xA0;pd.DataFrame()
df['date']&#xA0;=&#xA0;pd.date_range('2019-01-01&#xA0;01:40:00',&#xA0;'2019-01-01&#xA0;10:00:00',&#xA0;freq='1h')&#xA0;#&#xA0;&#x6309;&#x5C0F;&#x65F6;
df

date02019-01-01 01:40:0012019-01-01 02:40:0022019-01-01 03:40:0032019-01-01 04:40:0042019-01-01 05:40:0052019-01-01 06:40:0062019-01-01 07:40:0072019-01-01 08:40:0082019-01-01 09:40:00

4.2 文本转化为时间

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

date001/05/2016101/28/2017202/21/2018303/05/2018411/24/2019

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

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

date02016-01-0512017-01-2822018-02-2132018-03-0542019-11-24

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

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

date001/05/16101/28/17202/21/18303/05/18411/24/19

df['date']&#xA0;=&#xA0;pd.to_datetime(df['date'],&#xA0;format='%m/%d/%y')&#xA0;#&#xA0;&#x6CE8;&#x610F;y&#x5C0F;&#x5199;
df

date02016-01-0512017-01-2822018-02-2132018-03-0542019-11-24

4.3 间隔时间选取

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

date02018-12-0112018-12-0222018-12-0332018-12-0442018-12-0552018-12-0662018-12-0772018-12-0882018-12-0992018-12-10102018-12-11112018-12-12122018-12-13132018-12-14142018-12-15152018-12-16162018-12-17172018-12-18182018-12-19192018-12-20

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

df[df.index%3&#xA0;==&#xA0;0]&#xA0;#&#xA0;&#x540E;&#x7EED;&#x53EF;&#x4EE5;&#x6839;&#x636E;&#x9700;&#x6C42;&#x51B3;&#x5B9A;&#x662F;&#x5426;&#x8981;reset_index

date02018-12-0132018-12-0462018-12-0792018-12-10122018-12-13152018-12-16182018-12-19

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

df&#xA0;=&#xA0;pd.DataFrame({'date':&#xA0;list(pd.date_range('2018-12-01',&#xA0;'2018-12-10'))&#xA0;+&#xA0;list(pd.date_range('2018-12-15',&#xA0;'2018-12-20'))})
df&#xA0;#&#xA0;12-11&#xA0;&#x5230;&#xA0;12-14&#xA0;&#x662F;&#x7F3A;&#x5931;&#x7684;

date02018-12-0112018-12-0222018-12-0332018-12-0442018-12-0552018-12-0662018-12-0772018-12-0882018-12-0992018-12-10102018-12-15112018-12-16122018-12-17132018-12-18142018-12-19152018-12-20

df[df['date'].isin(pd.date_range('2018-12-01',&#xA0;'2018-12-20',&#xA0;freq='3D'))]

date02018-12-0132018-12-0462018-12-0792018-12-10112018-12-16142018-12-19

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

4.3 获取某个日期是星期几

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

date02019-01-0112019-01-0222019-01-0332019-01-0442019-01-0552019-01-0662019-01-0772019-01-0882019-01-0992019-01-10

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

from&#xA0;datetime&#xA0;import&#xA0;datetime
df['weekday']&#xA0;=&#xA0;df['date'].apply(datetime.weekday)&#xA0;
df

dateweekday02019-01-01112019-01-02222019-01-03332019-01-04442019-01-05552019-01-06662019-01-07072019-01-08182019-01-09292019-01-103

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

Pandas非常用技巧汇总

Pandas非常用技巧汇总

源码:

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

https://github.com/SilenceGTX/pandas_tricks

Pandas非常用技巧汇总

Pandas非常用技巧汇总

Original: https://blog.csdn.net/yanqianglifei/article/details/115289274
Author: 致Great
Title: Pandas非常用技巧汇总

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/755799/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球