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 x: type(x) == int)]
A019881199342018
3.11 总体统计
df = pd.DataFrame({'A':[1, 2, 1, 3], 'B':[2, 1, 1, 4], 'C':[3, 4, 1, 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 = pd.DataFrame({'signal': [1, 2, 3, 2, 5, 3, 5, 6, 3, 2, 3, 5, 6, 2, 1, 3, 2]})
df
signal01122332455365768392103115126132141153162
我们都知道rolling能提供滑窗功能,辅以各种统计函数就能实现滑窗均值、滑窗最值等等功能。但有时候我们的需求可能会更复杂一些,而rolling原生的统计函数并不多,不过幸好,我们有apply。假设我们要统计的窗口长度为5,每个窗口内统计最大top 3的和:
df['rolling_top3_sum'] = df['signal'].rolling(5).apply(lambda x: 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 = pd.DataFrame({'A':['a', 'b', 'c', 'd'], 'B':[2, 1, 1, 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 = pd.DataFrame({'A':[1, 1, 1, 2, 3, 1, 3, np.nan], 'B':[1, 1, 1, 1, 1, 2, 2, 2]})
df
AB01.0111.0121.0132.0143.0151.0263.027NaN2
假设我们希望有一列标记A中每个元素出现的次数(即Count Encoding),一个比较简单的方法是先将value_counts的结果转化为dict,再利用map函数:
tmp = df['A'].value_counts().to_dict()
df['A_cnt'] = 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 = pd.DataFrame({'A':[1, 1, 2, 3, 5], 'B':[2, 1, 1, 2, 4], 'C':[5, 4, 3, 2, 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, 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) # 第一个参数为需要执行的操作,第二个参数为列表
Am1m2m30a1.0NaN4.01b2.03.0NaN2c3.04.02.03dNaN3.0NaN4fNaNNaN1.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_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 = ['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()
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 = 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
product2020-01-012020-01-022020-01-030A5241B1232C336
假设我们有一些销售数据,每一天的记录都是单独的一列,而我们想将其转化成所有日期都在同一列的样子,我们可以使用行转列的melt函数:
pd.melt(df, 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 = pd.DataFrame()
df
df['date'] = pd.date_range('2019-01-01', '2019-01-06') # 按日,通过起止时间
df
date02019-01-0112019-01-0222019-01-0332019-01-0442019-01-0552019-01-06
另一种方法是通过开始时间和时长,如下:
df = pd.DataFrame()
df['date'] = pd.date_range('2019-01-01', periods=10) # 按日,从2019年1月1日起,时长为10天
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 = pd.DataFrame()
df['date'] = pd.date_range('2019-01-01 01:40:00', '2019-01-01 10:00:00', freq='1h') # 按小时
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 = pd.DataFrame({'date': ['01/05/2016', '01/28/2017', '02/21/2018', '03/05/2018', '11/24/2019']})
df
date001/05/2016101/28/2017202/21/2018303/05/2018411/24/2019
我们经常会在表格中遇到各种代表时间的文本,有时为了后续操作方便,我们需要将其转化为python可以识别的时间信息(比如Timestamp),我们可以使用pd.to_datetime:
df['date'] = pd.to_datetime(df['date'], format='%m/%d/%Y')
df
date02016-01-0512017-01-2822018-02-2132018-03-0542019-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
date001/05/16101/28/17202/21/18303/05/18411/24/19
df['date'] = pd.to_datetime(df['date'], format='%m/%d/%y') # 注意y小写
df
date02016-01-0512017-01-2822018-02-2132018-03-0542019-11-24
4.3 间隔时间选取
df = pd.DataFrame({'date': pd.date_range('2018-12-01', '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 == 0] # 后续可以根据需求决定是否要reset_index
date02018-12-0132018-12-0462018-12-0792018-12-10122018-12-13152018-12-16182018-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 是缺失的
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', '2018-12-20', freq='3D'))]
date02018-12-0132018-12-0462018-12-0792018-12-10112018-12-16142018-12-19
逐段分解上述命令:
(1)首先,最里面的括号,我们创建了一个日期索引,首尾与df中的日期对齐,间隔为3天;
(2)然后我们选取df的date列中存在于上述日期索引的行。
4.3 获取某个日期是星期几
df = pd.DataFrame()
df['date'] = pd.date_range('2019-01-01', 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 datetime import datetime
df['weekday'] = df['date'].apply(datetime.weekday) 
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代表星期一。
源码:
https://www.kesci.com/mw/project/604a285c74dfc60016e0fda0
https://github.com/SilenceGTX/pandas_tricks
Original: https://blog.csdn.net/yanqianglifei/article/details/115289274
Author: 致Great
Title: Pandas非常用技巧汇总
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/755799/
转载文章受原作者版权保护。转载请注明原作者出处!