学习笔记

pandas高级操作:数据复杂查询与筛选、数据类型转换、数据排列、添加修改、高级过滤、数据迭代、函数应用

1.复杂查询

逻辑运算

以DataFrame中的一列进行逻辑运算,可以产生一个由布尔值组成的Series,真假值由此位置上的 数据是否满足逻辑表达式决定。比如df.列标签>数值。

一个针对索引的逻辑表达式会产生一个array类型数组,该数组由布尔值组成,

比如df.index==会返回[False True False False False False]。

可以结合loc[]方法,取DataFrame中的部分进行逻辑运算,比如df[:,’列标签1′:’列标签2′]>0,因为一些非数值类型的列与数值比大小可能会报错

除了逻辑运算,pandas孩子后慈湖组合条件的Python为运算,比如:

~(df.列标签1<60) & (df[‘列标签2′]==’文本’)

#【~】表示取反,这行代码的意思是列标签1不低于60 并且 列标签2等于指定内容

逻辑筛选数据

切片([]) 和 .loc[]还有.iloc[]均支持上文所介绍的逻辑表达式,通过逻辑表达式进行复杂条件的数据筛选时需要注意,表达式输出的结果必须是一个布尔序列或者符合其格式要求的数据形式。

例如df.iloc[1+1]和df.iloc[lambda df:len(df)-1]计算出一个数值,符合索引的格式,df.iloc[df.index==8]返回的事一个布尔序列,df.iloc[df.index]返回的是一个索引,它们都是有效的表达式。

以下是切片([])的逻辑筛选示例,返回结果是符合条件的行组成的DataFrame

df[df['列标签'] == 8] # 列标签等于8
df[~(df['列标签'] == 8)] # 不等于8
df[df.列标签 == '文本'] # 列标签的内容为某段文本
df[df.列标签1 > df.列标签2]	#返回列标签1的数值大于列标签2数值的行

以下是.loc[]的一些示例。需要注意的是,【|】或、【&】与以及【~】非的各个独立逻辑表达式要用括号括起来。

# 表达式与切片一致
df.loc[df['列标签1'] > 90, '列标签2':]  # 先筛选出列标签1>90的行,然后只返回从列标签2开始的所有列的DataFrame
df.loc[(df.列标签1 > 80) & (df.列标签2 < 20)] # and关系	#返回列标签1>80并且列标签2小于20的行组成的DataFrame
df.loc[(df.Q1 > 90) | (df.Q2 < 90)] # or关系
df.loc[df['Q1'] == 8] # 等于8
df.loc[df.Q1 == 8] # 等于8
df.loc[df['Q1'] > 90, 'Q1':] # Q1大于90,显示Q1及其后所有列

any和all对逻辑计算后的布尔序列再进行判断,序列中所有的值都为True时all才返回True,序列中只要有一个值为True时any就返回True。

它们还可以传入axis参数的值,用于指定判断的方向,与pandas的axis参数整体约定一样,默认为0表示列方向,传入1为行方向,利用这两个方法,我们可以对整体数据进行逻辑判断,例如:

df[(df.loc[:,['列标签1','列标签2']]>60).all(1)]	#数据表中列标签1与列标签2都大于60的行组成的 DataFrame
df[(df.loc[:,['列标签1','列标签2']]>60).any(1)]	#返回列标签1与列标签2中至少1个数值大于60的行

上栗对两个列整体先做2逻辑计算得到一个2列的布尔序列,然后用all或any在行方向上作品逻辑运算。

函数筛选

可以在表达式处使用lambda函数,默认变量是其操作的对象。如果操作的对象是一个DataFrame,那么变量就是这个DataFrame;如果是一个Series,那么就是这个Series。

以下示例中,s就是指df.Q1这个Series:

# 查询最大索引的值
df.Q1[lambda s: max(s.index)] # 值为21
# 计算最大值
max(df.Q1.index) # 99
df.Q1[df.index==99]
'''
99    21
Name: Q1, dtype: int64
'''

下面是一些示例:

df[lambda df: df['Q1'] == 8] # Q1为8的
df.loc[lambda df: df.Q1 == 8, 'Q1':'Q2'] # Q1为8的,显示 Q1、Q2
df.loc[:, lambda df: df.columns.str.len()==4] # 由真假值组成的序列
df.loc[:, lambda df: [i for i in df.columns if 'Q' in i]] # 列名列表
df.iloc[:3, lambda df: df.columns.str.len()==2] # 由真假值组成的序列

比较函数

pandas提供了一些比较函数,使我们可以将逻辑表达式替换为函数形式

# 以下相当于 df[df.Q1 == 60]
df[df.Q1.eq(60)]
'''
     name team  Q1  Q2  Q3  Q4
20  Lucas    A  60  41  77  62
'''

除了.eq(),还有:

df.ne() # 不等于 !=
df.le() # 小于等于 <=
df.lt() # 小于 <
df.ge() # 大于等于 >=
df.gt() # 大于 >

示例:

df[df.Q1.ne(89)] # Q1不等于89
df.loc[df.Q1.gt(90) & df.Q2.lt(90)] # and关系,Q1>90,Q2<90

这些函数可以传入一个定值、数列、布尔序列、Series或DataFrame,来与原数据比较。

另外还有一个.isin()函数,用于判断数据是否包含指定内容 。可以传入一个列表,原数据只需要满足其中一个存在即可,也可以传入一个字典,键为列名,值为需要匹配的值,以涉嫌按列个性化匹配存在值。

df[df.team.列标签(['文本1','文本2'])]
#从指定的列中,判断是否包含文本1或文本2,返回所有符合条件的行

df.isin({'列标签1':['文本1','文本2'],'列标签2':[数值,数值]})
#列标签1中包含文本1或文本2的单元格设为True,列标签2中,包含数值1或数值2的单元格标位True,整张数据表其他位置全都标为False
#返回一张全是True或False的数据表

查询df.query()

df.query(expr)使用布尔表达式查询DataFrame的列,表达式是一个字符串,类似于SQL中的where语句,不过它相当灵活。

#Q1、Q22都是列标签,会返回符合条件的行组成DataFrame

df.query('Q1 > Q2 > 90') # 直接写类型SQL where语句
df.query('Q1 + Q2 > 180')
df.query('Q1 == Q2')
df.query('(Q1<50) & (Q2>40) and (Q3>90)')
df.query('Q1 > Q2 > Q3 > Q4')
df.query('team != "C"')
df.query('team not in ("E","A","B")')
# 对于名称中带有空格的列,可以使用反引号引起来
df.query('B == `team name`')

还支持使用@符号引入变量:

# 支持传入变量,如大于平均分40分的
a = df.Q1.mean()
df.query('Q1 > @a+40')
df.query('Q1 > `Q2`+@a')

df.eval()与df.query类似,也可以用于表达式筛选:

# df.eval()用法与df.query类似
df[df.eval("Q1 > 90 > Q3 > 10")]
df[df.eval("Q1 > `Q2`+@a")]

筛选df.filter()

df.filter()可以对行名和列名进行筛选,支持模糊匹配和正则表达式。

df.filter(items=['Q1', 'Q2']) # 选择两列
df.filter(regex='Q', axis=1) # 列名包含Q的列
df.filter(regex='e$', axis=1) # 以e结尾的列
df.filter(regex='1$', axis=0) # 正则,索引名以1结尾
df.filter(like='2', axis=0) # 索引中有2的
# 索引中以2开头、列名有Q的
df.filter(regex='^2', axis=0).filter(like='Q', axis=1)

按数据类型查询

pandas提供了一个按列数据类型筛选的功能df.select_dtypes(include=None,exclude=None),它可以指定包含和不包含的数据类型,如果只有一个类型,传入字符,如果有多个类型,传入列表:

df.select_dtypes(include=['float64']) # 选择float64型数据的列
df.select_dtypes(include='bool')	
df.select_dtypes(include=['number']) # 只取数字型的列
df.select_dtypes(exclude=['int']) # 排除int类型的列,其他的列作为一个DataFrame返回
df.select_dtypes(exclude=['datetime64'])

2.数据类型转换

在分析数据钱,我们需要为数据分配好合适的类型,这样才能够高效地处理数据,不同的数据类型适用于不同的处理办法。

在加载数据时,就可以通过dtype参数指定数据各列的类型,比如:

# 对所有字段指定统一类型
df = pd.DataFrame(data, dtype='float32')
# 对每个字段分别指定
df = pd.read_excel(data, dtype={'team': 'string', 'Q1': 'int32'})

推断类型

pandas可以用 以下方法智能地推断各列的数据类型,会返回一个按推断修改后的DataFrame。如果需要使用这些类型的数据,可以赋值替换。

# 自动转换合适的数据类型
df.infer_objects() # 推断后的DataFrame
df.infer_objects().dtypes
'''
name    object
team    object
Q1       int64
Q2       int64
Q3       int64
Q4       int64
dtype: object
'''

# 推荐这个新方法,它支持string类型
df.convert_dtypes() # 推断后的DataFrame
df.convert_dtypes().dtypes
'''
name    string
team    string
Q1       Int64
Q2       Int64
Q3       Int64
Q4       Int64
dtype: object
'''

指定类型

pd.to_XXX系统方法可以将数据安全转换,errors参数可以实现 无法转换则转换为兜底类型:

# 按大体类型推定
m = ['1', 2, 3]
s = pd.to_numeric(s) # 转成数字
pd.to_datetime(m) # 转成时间
pd.to_timedelta(m) # 转成时间差
pd.to_datetime(m, errors='coerce') # 错误处理
pd.to_numeric(m, errors='ignore')
pd.to_numeric(m errors='coerce').fillna(0) # 兜底填充
pd.to_datetime(df[['year', 'month', 'day']]) # 组合成日期

转换为数字类型时,默认返回的dtype是float64还是int64取决于提供的数据。市容downcast参数获得向下转换后的其它类型。

# 最低期望
pd.to_numeric(m, downcast='integer') # 至少为有符号int数据类型
# array([1, 2, 3], dtype=int8)
pd.to_numeric(m, downcast='signed') # 同上
# array([1, 2, 3], dtype=int8)
pd.to_numeric(m, downcast='unsigned') # 至少为无符号int数据类型
# array([1, 2, 3], dtype=uint8)
pd.to_numeric(m, downcast='float') # 至少为float浮点类型
# array([1., 2., 3.], dtype=float32)

可以应用在函数中:

df = df.select_dtypes(include='number')
# 应用函数
df.apply(pd.to_numeric)

类型转换astype()

astype()是最常见也是最通用的数据类型转换方法,一般我们使用astype()操作数据转换就可以了。

df.Q1.astype('int32').dtypes
# dtype('int32')
df.astype({'Q1': 'int32','Q2': 'int32'}).dtypes
'''
Q1    int32
Q2    int32
Q3    int64
Q4    int64
dtype: object
'''

以下是一些使用示例:

df.index.astype('int64') # 索引类型转换
df.astype('int32') # 所有数据转换为int32
df.astype({'col1': 'int32'}) # 指定字段转指定类型
s.astype('int64')
s.astype('int64', copy=False) # 不与原数据关联
s.astype(np.uint8)
df['name'].astype('object')
data['Q4'].astype('float')
s.astype('datetime64[ns]')
data['状态'].astype('bool')

当数据的格式不具备转换为目标类型的条件时,需要先对数据进行处理。例如“89.3%”是一个字符串,要转换为数字,要先去掉百分号:

# 将"89.3%"这样的文本转为浮点数
data.rate.apply(lambda x: x.replace('%', '')).astype('float')/100

转换为时间类型

我们通常使用pd.to_datetime()和s.astype(‘datetime64[ns]’)来做时间类型转换

t = pd.Series(['20200801', '20200802'])
t
'''
0    20200801
1    20200802
dtype: object
'''
pd.to_datetime(t)
'''
0   2020-08-01
1   2020-08-02
dtype: datetime64[ns]
'''
t.astype('datetime64[ns]')
'''
0   2020-08-01
1   2020-08-02
dtype: datetime64[ns]
'''

3.数据排列

数据排序是按一定的顺序将数据重新排列,帮助使用者发现数据的变化趋势,同时提供一定的业务线索,还具有对数据纠错、分类等作用。

排序索引

df.sort_index()可以实现按索引排序,默认以从小到大的升序方式进行排列,如果希望按降序排列,需要传入参数ascending=False:

按列索引名排序,也就是对表头这一行进行排序,可以通过axis=1参数,

比如:df.sort_index(axis=1,ascending=False,inplace=True)

更多方法如下:

#S表示Series,df表示DataFrame
s.sort_index() # 升序排列
df.sort_index() # df也是按索引进行排序
df.team.sort_index()
s.sort_index(ascending=False) # 降序排列
s.sort_index(inplace=True) # 排序后生效,改变原数据
# 索引重新0-(n-1)排,很有用,可以得到它的排序号
s.sort_index(ignore_index=True)
s.sort_index(na_position='first') # 空值在前,另'last'表示空值在后
s.sort_index(level=1) # 如果多层,排一级
s.sort_index(level=1, sort_remaining=False) # 这层不排
# 行索引排序,表头排序
df.sort_index(axis=1) # 会把列按列名顺序排列

df.reindex()可以指定自己定义顺序的索引,实现行和列的顺序重新定义:

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

# 按要求重新指定索引顺序
df.reindex(['c', 'b', 'a'])
'''
   A  B
c  4  6
b  2  5
a  1  3
'''

# 指定列顺序
df.reindex(['B', 'A'], axis=1)
'''
   B  A
a  3  1
b  5  2
c  6  4
'''

数值排序

数据值的排序主要使用sort_values(),数字按大小排序,字符按字母排序。Series和DataFrame都支持此方法。

df.列标签.sort_values() #将DataFrame中的一列进行排序并返回Series

针对DataFrame,使用此方法时要传入一个或多个排序的列名:df.sort_values(‘列标签’)

默认排序是升序,也就是从小到大,但可以指定排序方式,下例先按team列标签升序排序,如果这一列中出现相同的值,再按name列标签降序排序:

df.sort_values(by=[‘team’,’name’],ascending=[True,False])

其他常用方法:

s.sort_values(ascending=False) # 降序
s.sort_values(inplace=True) # 修改生效
s.sort_values(na_position='first') # 空值在前
# df按指定字段排列
df.sort_values(by=['team'])
df.sort_values('Q1')
# 按多个字段,先排team,在同team内再看Q1
df.sort_values(by=['team', 'Q1'])
# 全降序
df.sort_values(by=['team', 'Q1'], ascending=False)
# 对应指定team升Q1降
df.sort_values(by=['team', 'Q1'], ascending=[True, False])
# 索引重新0-(n-1)排
df.sort_values('team', ignore_index=True)

混合排序

有时候需要用索引和数据值混合排序。下例中假如name是索引,我们需要先按team列标签排序,再按索引排序:

df.set_index('name', inplace=True) # 设置name为索引
df.index.names = ['s_name'] # 给索引起名
df.sort_values(by=['s_name', 'team']) # 排序
'''
        team  Q1  Q2  Q3  Q4
name
Aaron      A  96  75  55   8
Ack        A  57  60  18  84
Acob       B  61  95  94   8
Adam       C  90  32  47  39
Aiden      D  20  31  62  68
...      ...  ..  ..  ..  ..
Toby       A  52  27  17  68
Tommy      C  29  44  28  76
Tyler      A  75  16  44  63
William    C  80  68   3  26
Zachary    E  12  71  85  93

[100 rows x 5 columns]
'''

以下方法也可以实现上述需求,不过要注意顺序:

# 设置索引,按team排序,再按索引排序

df.set_index(‘name’).sort_values(‘team’).sort_index()

另外,还可以使用df.reindex(),通过给定新的索引方式来排名,按照这个思路可以实现人工指定任意顺序。

# 按姓名排序后取出排名后的索引列表
df.name.sort_values().index
'''
Int64Index([88,  2,  6, 33, 94, 83, 57, 63, 32, 12, 41,  1, 22, 96, 99, 44, 71,
            52, 67, 86, 49, 91, 28, 56, 76, 42, 30, 98, 38, 73, 78, 81,  3, 21,
            89, 27, 82, 53, 95, 92, 39,  5, 25, 64, 17, 51, 68, 24, 61, 47, 15,
            93, 36, 66, 50, 31, 16, 43, 84, 10, 90, 58,  7, 85, 97,  0, 11, 48,
            87, 59, 20, 72, 23, 62, 19, 77, 70,  4, 54,  9,  8, 34, 65, 29, 74,
            60, 45, 80, 35, 37, 75, 26, 13, 69, 14, 40, 46, 79, 18, 55],
           dtype='int64')
'''
# 将新的索引应用到数据中
df.reindex(df.name.sort_values().index)
'''
       name team  Q1  Q2  Q3  Q4
88    Aaron    A  96  75  55   8
2       Ack    A  57  60  18  84
6      Acob    B  61  95  94   8
33     Adam    C  90  32  47  39
94    Aiden    D  20  31  62  68
..      ...  ...  ..  ..  ..  ..
40     Toby    A  52  27  17  68
46    Tommy    C  29  44  28  76
79    Tyler    A  75  16  44  63
18  William    C  80  68   3  26
55  Zachary    E  12  71  85  93

[100 rows x 6 columns]
'''

按值大小排序

nsmallest()和nlargest()用来实现数字列的排序,并可指定返回的个数:

#指定返回的行数,然后根据2个列标签排序,先按列标签1最小的在前,如果出现相同的值,那么列标签2最小的在前:

df.nsmallest(返回的行数,[‘列标签1′,’列标签2’])

其他示例:

s.nsmallest(3) # 最小的3个
s.nlargest(3) # 最大的3个
# 指定列
df.nlargest(3, 'Q1')
df.nlargest(5, ['Q1', 'Q2'])
df.nsmallest(5, ['Q1', 'Q2'])

4.添加修改

对数据的修改、增加和删除在数据整理过程中时常发生。修改的情况一般是修改错误,还有一种情况是格式转换,如把中文数字改为阿拉伯数字,修改也会涉及数据的类型修改。

删除一般会通过筛选的方式,筛选完成后将最终的结果重新赋值给变量,达到删除的目的。增加行和列是最为常见的操作,数据分析过程中会计算出新的指标以新列展示。

修改数值

在pandas中修改数值只需要先筛选出需要修改的数值范围,再为这个范围重新赋值。

没有返回值也不需要inplace=True

df.iloc[0,0]=’Lily’ #将第0行第0列的位置修改为Lily

df.loc[行索引:行索引,’列标签’:’列标签’]=要修改成什么值 #将数据表中一大块内容全都修改成某个值

df[df.列标签<60]=’小于60′ #理论上应该是对该列下面小于60的单元格替换成指定的值,但是试了之后却发现是:指定的列下面,如果某一个单元格小于60,那么该单元格所在的整一行的所有单元格,内容都会被替换

以上操作df变量的内容被修改,这里指定的是一个定值,所有满足条件的数据均被修改为这个定值。还可以传一个同样形状的数据来修改值:

可以将一个列表的内容 将DataFrame中的一列的内容替换掉,但列表的元素个数必须与DataFrame要被替换的一列的单元格数量相一致:

#df有6行(列标签下面有6个元素)

v=[1,2,3]*2

df.列标签=v

可以用一个DataFrame的内容替换掉另一个DataFrame中的内容,但是,只有两张表上列标签与索引值相同的 内容,才会发生替换,且可以指定要被替换的DataFrame的范围,范围外的内容不会被修改。

df.loc[1:3,’name’:’team’]=df2 #通过loc参数锁定范围,然后放入另一张表相同位置的内容,

在指定范围内的所有单元格,会传入另一张表相同列标签与相同索引值单元格的内容,如果在df2的对应位置没有内容,则会被填充NaN

替换数据replace

replace方法可以对数据进行批量替换,直接修改原数据表需要inplace=True

s.replace(0, 5) # 将列数据中的0换为5
df.replace(0, 5) # 将数据中的所有0换为5
df.replace([0, 1, 2, 3], 4) # 将0~3全换成4
df.replace([0, 1, 2, 3], [4, 3, 2, 1]) # 对应修改

# {'pad', 'ffill', 'bfill', None} 试试
s.replace([1, 2], method='bfill') #向下填充
# 向下填充:如果在DataFrame中某个单元格的值与列表中的值对应,那么DataFrame中的值会被替换为它下面单元格的值
#如果下面没有值,则保持不变
#ffill向上填充,复制上面单元格,pad与None暂时没搞清楚

df.replace({0: 10, 1: 100}) # 字典对应修改
df.replace({'列标签': 0, 'Q2': 5}, 100) # 将指定字段的指定值修改为100
df.replace({'列标签': {0: 100, 4: 400}}) # 将指定列里的指定值替换为另一个指定的值
# 使用正则表达式
df.replace(to_replace=r'^ba.$', value='new', regex=True)
df.replace({'A': r'^ba.$'}, {'A': 'new'}, regex=True)
df.replace(regex={r'^ba.$': 'new', 'foo': 'xyz'})
df.replace(regex=[r'^ba.$', 'foo'], value='new')

填充空值fillna

fillna对空值填入指定数据,通常用于数据清晰。还有一种做法是删除有空值的数据

df.fillna(0) # 将空值全修改为0
# {'backfill', 'bfill', 'pad', 'ffill', None}, 默认为None
df.fillna(method='ffill') # 将空值都修改为其前一个值
values = {'A': 0, 'B': 1, 'C': 2, 'D': 3}
df.fillna(value=values) # 为各列填充不同的值
df.fillna(value=values, limit=1) # 只替换第一个

修改列标签与行索引

修改索引名最简单也最常用的方法就是将df.index和df.columns重新赋值为一个类似于列表的序列值,这会将其覆盖为指定序列中的名称。

使用df.rename和df.rename_axis对轴名称进行修改,在原数据表上修改要加上inplace=True

df.rename(columns={‘原列标签’:’修改后的列标签’})

以下是一些常用方法:

df.rename(columns={"Q1": "a", "Q2": "b"}) # 对表头进行修改
df.rename(index={0: "x", 1: "y", 2: "z"}) # 对索引中对应的值进行进行修改

df.rename(index=str) # 对类型进行修改
df.rename(str.lower, axis='columns') # 传索引类型	#将列标签全部改成大写upper或小写
df.rename({1: 11, 2: 22}, axis='index')	#对指定的行索引进行修改,1改成11,2改成22

# 对索引名进行修改
s.rename_axis("animal")	
df.rename_axis("animal") # 默认是列索引
df.rename_axis("索引名", axis="columns") # 指定行索引	#修改行标签这一行的索引行
# 索引为多层索引时可以将type修改为class
df.rename_axis(index={'type': 'class'})

# 可以用set_axis进行设置修改行索引或列标签
#axis=0或'index'表示修改行标签(索引列的内容 )
#axis=1或'columns'表示修改列标签(表头)
#指定的新索引要与原数据表的行数或列数对应,
#在未来inplace=True的语法可能被放弃
s.set_axis(['a', 'b', 'c'], axis=0)		#修改索引列的内容
df.set_axis(['I', 'II'], axis='columns')	#修改列标签
df.set_axis(['i', 'ii'], axis='columns', inplace=True)	
#FutureWarning: DataFrame.set_axis 'inplace' keyword is deprecated and will be removed in a future version. Use `obj = obj.set_axis(..., copy=False)` instead
  df.set_axis([11,2,3,4,5,6], axis='index',inplace=True)

增加列

增加列是数据处理中最常见的操作,pandas可以像定义一个变量一样定义DataFrame中新的列,新列可以使一个定值,所有行都为此值,也可以是一个同等长度的序列数据,各行有不同的值。

df[‘新列’]=df.旧列1+df.旧列2 #创建新列,并给每列复制对应行列1+列2的和

df.[‘新列’]=df.sum(1) #加上参数1好像可以忽略掉非数值类型的列,该方法有futurewarning

还可以在筛选数据时传入一个不存在的列,并为其赋值以增加新列,如df.loc[:, ‘新列’] = 10,虽然原数据表中”新列”不存在,但我们赋值为10,就会增加一个名为“新列”并且值全是10的列。

一下是创建新列的常用方法:

df['foo'] = 100 # 增加一列foo,所有值都是100
df['foo'] = df.Q1 + df.Q2 # 新列为两列相加
df['foo'] = df['Q1'] + df['Q2'] # 同上
# 把所有为数字的值加起来
df['total'] = df.select_dtypes(include=['int']).sum(1)
df['total'] = df.loc[:,'Q1':'Q4'].apply(lambda x: sum(x), axis='columns')
df.loc[:, 'Q10'] = '我是新来的' # 也可以
# 增加一列并赋值,不满足条件的为NaN
df.loc[df.数字列 >= 60, '成绩'] = '合格'	#查找“成绩”列,如果没有则创建,如果数字列的数值大于等于60,成绩列就显示合格,不满足条件的显示NaN
df.loc[df.数字列 < 60, '成绩'] = '不合格'

插入列df.insert()

pandas提供了insert()方法来为DataFrame插入一个新列,insert()方法可以传入3个主要参数:

第一个参数loc是数字,代表新列所在的位置,使用列的数字索引,如0为第一列;

第二个参数column为新的列名;

最后一个参数value为列的值,一般是一个Series

df.insert(1,’新列’,df.sum(1)) #在表头这一行索引值为1的位置,插入“新列”列标签,每个单元格填充同一行内其他所有数值型列的和

如果已经存在相同的数据列会报错,可传入allow_duplicates=True参数插入一个同名的列,如果希望新列位于最后,可以在第一个参数位loc传入len(df.columns)

指定列df.assign()

df.assign(k=v)其指定一个新列的操作,k为新列的列名,v为此列的值,v必须是一个与原数据通索引的Series,它在链式编程技术中相当重要,因此这里专门介绍一下。

我们平时再做数据分析时会增加一些临时列,如果新列全部使用赋值的方式生成,则会造成原数据混乱,因此就需要一个方法来让我们不用赋值也可以创建一个临时的列,这种思路适用于所有对原数据的操作,建议在未最终确定数据处理方案时,除了必要的数据整理工作,均使用链式方法。

# 使用了链式方法
(
    df.assign(total=df.sum(1)) # 总成绩
    .assign(Q=100) # 目标满分值
    .assign(name_len=df.name.str.len()) # 姓名长度
    .assign(avg=df.mean(1)) # 平均值
    .assign(avg2=lambda d: d.total/4) # 平均值2
)
'''
        name team  Q1  Q2  Q3  Q4  total    Q  name_len    avg   avg2
0      Liver    E  89  21  24  64    198  100         5  49.50  49.50
1       Arry    C  36  37  37  57    167  100         4  41.75  41.75
2        Ack    A  57  60  18  84    219  100         3  54.75  54.75
3      Eorge    C  93  96  71  78    338  100         5  84.50  84.50
4        Oah    D  65  49  61  86    261  100         3  65.25  65.25
..       ...  ...  ..  ..  ..  ..    ...  ...       ...    ...    ...
95   Gabriel    C  48  59  87  74    268  100         7  67.00  67.00
96   Austin7    C  21  31  30  43    125  100         7  31.25  31.25
97  Lincoln4    C  98  93   1  20    212  100         8  53.00  53.00
98       Eli    E  11  74  58  91    234  100         3  58.50  58.50
99       Ben    E  21  43  41  74    179  100         3  44.75  44.75

[100 rows x 11 columns]
'''

以上是使用了链式方法的典型代码形式,后期会以这种风格进行代码编写。特别要说明的是avg2列的计算过程,因为df实际是没有total这一列的,如果我们需要使用total列,就需要用lambda来调用。lambda中第一个变量d是代码执行到本行前的DataFrame内容,可以认为是一个虚拟的DataFrame实体,然后用变量d使用这个DataFrame的数据。作为变量名,d可以替换为其他任意合法的名称,但为了代码可读性,建议使用d,代表它是一个DataFrame。如果是Series,建议使用s。

以下是其他一些使用示例:

df.assign(Q5=[100]*100) # 新增加一列Q5
df = df.assign(Q5=[100]*100) # 赋值生效
df.assign(Q6=df.Q2/df.Q1) # 计算并增加Q6
df.assign(Q7=lambda d: d.Q1 * 9 / 5 + 32) # 使用lambda
# 添加一列,值为表达式结果:True或False
df.assign(tag=df.Q1>df.Q2)
# 比较计算,True为1,False为0
df.assign(tag=(df.Q1>df.Q2).astype(int))
# 映射文案
df.assign(tag=(df.Q1>60).map({True:'及格',False:'不及格'}))
# 增加多个
df.assign(Q8=lambda d: d.Q1*5,
         Q9=lambda d: d.Q8+1) # Q8没有生效,不能直接用df.Q8

执行表达式df.eval()

df.eval()与之前介绍过的df.query()一样,能够以字符的形式传入表达式,增加列数据。

df.eval(‘新列=旧列1+旧列2’,inplace=True) #创建一个“新列”,数据为旧列1与旧列2的和

其他常用方法如下:

df['C1'] = df.eval('Q2 + Q3')
df.eval('C2 = Q2 + Q3') # 计算
a = df.Q1.mean()
df.eval("C3 = `Q3`+@a") # 使用变量
df.eval("C3 = Q2 > (`Q3`+@a)") # 加一个布尔值
df.eval('C4 = name + team', inplace=True) # 立即生效

增加行

可以使用loc[]指定索引给出所有列的值来增加一行数据。

假设当前DataFrame的最大索引为5,增加一条索引为6的行:

df.loc[6]=[‘ABC’,’A’,1,2,3,3] #后面列表中的内容会以一行的形式加入到DataFrame最后一行,列表中的元素个数要与DataFrame中的列数相匹配。

以下是一些其他用法:

df.loc[101]={'Q1':88,'Q2':99} # 指定列,无数据列值为NaN
df.loc[df.shape[0]+1] = {'Q1':88,'Q2':99} # 自动增加索引#线通过shape获取行数与列数,然后设索引值为行数+1,为这一行指定列添加指定内容,如果其他列没有指定内容会被填充NaN
df.loc[len(df)+1] = {'Q1':88,'Q2':99}
# 批量操作,可以使用迭代
rows = [[1,2],[3,4],[5,6]]
for row in rows:
    df.loc[len(df)] = row

追加合并

增加行数据的使用场景相对较少,一般是采用数据追加的模式。

df.append()可以追加一个新行,或者将2张数据表合并:

df=df.append(df2) 将df与df2合并,或者说将df2追加给df,并返回给df,该方法在未来会被启用

pd.conctat可以将两个DataFrame或Series相连接:

用法是:df=pd.concat([df,df2],ignore_index=True)

ignore_index=True可以为合并后的结果生成新的index

如果2张数据表的列数与行数不一致,一张数据表的有的列另一张数据表没有,合并后会产生数据缺失的区块,会用NaN来填充

#合并2个Series
s1=pd.Series(['A','B'])
s2=pd.Series(['C','D'])
a=pd.concat([s1,s2])
print(a)
#如果没有ignore_index=True参数,合并2个Series时,两个Series原有的index也会跟着合并并且保持不变,也就是说合并后的新series的index会出现重复值
pd.concat([s1, s2], ignore_index=True) # 索引重新编

# 原数索引不变,增加一个一层索引(keys里的内容),变成多层索引
pd.concat([s1, s2], keys=['s1', 's2'])
pd.concat([s1, s2], keys=['s1', 's2'],
          names=['Series name', 'Row ID'])
# df同理
pd.concat([df1, df2])
pd.concat([df1, df3], sort=False)
pd.concat([df1, df3], join="inner") # 只连相同列
pd.concat([df1, df4], axis=1) # 连接列

删除

删除有2种方法,一种是pop()函数,另一种是反选法。

使用pop(),Series会删除指定索引的数据同时返回这个被删除的值,DataFrame会删除指定的列并返回这个被删除的列。

df.pop(‘列标签’)

反选法是将需要的数据筛选出来赋值给原变量,相当于实现了删除。

删除空值 dropna()

df.dropna()可以删除空值、确实不全的数据

df.dropna() # 一行中有一个缺失值就删除
df.dropna(axis='columns') # 只保留全有值的列
df.dropna(how='all') # 行或列全没值才删除
df.dropna(thresh=2) # 至少有两个空值时才删除
df.dropna(inplace=True) # 删除并使替换生效

5.高级过滤

df.where()和df.mask()通过给定的条件对原数据是否满足条件进行筛选,最终返回与原数据形状相同的数据。

#只保留数字类型列:

df=df.select_dtypes(include=’number’)

df.where()与df.mask()都可以按条件筛选数据,df.where()将不满足条件的值替换为NaN,df.mask()将满足条件的值替换为NaN。np.where()是NumPy的一个方法,在满足条件和不满足条件的情况下都可指定填充值。

df.where()

df.where()中可以传入一个布尔表达式,布尔值的Series/DataFrame、序列或可调用的对象,然后与原数据做对比,返回一个行索引与列索引与原数据相同的数据,并且满足条件的位置保留原值,在不满足条件的位置填充NaN。

df.where(df!=0) #df表中,所有不等于0的单元格不做修改,不满足条件的单元格全都变成NaN

a=df.where(df.列标签 > 80) #返回结果中,如果指定列标签下的单元格大于80,该单元格所在的一行保留原样,其余行全部填充NaN,使用inplace=True好像会报错?

print(a)

传入一个可调用对象,下例中是lambda:

df.where(lambda d:d.列标签>10)

条件为一个布尔值的Series:

#传入布尔值Series,前三个为真

df.列标签.where(pd.Series([True]*3)) #只保留Series中前3个数

可以指定一个值或算法来替换NaN:

# 大于等于60分的显示成绩,小于的显示“不及格”

df.where(df>=60, ‘不及格’)

给定一个算法:

# c 定义一个数是否为偶数的表达式
c = df%2 == 0
# 传入c, 为偶数时显示原值减去20后的相反数
df.where(~c, -(df-20))
'''
    Q1  Q2  Q3  Q4
0   89  21  -4 -44
1  -16  37  37  57
2   57 -40   2 -64
3   93 -76  71 -58
4   65  49  61 -66
..  ..  ..  ..  ..
95 -28  59  87 -54
96  21  31 -10  43
97 -78  93   1   0
98  11 -54 -38  91
99  21  43  41 -54

[100 rows x 4 columns]
'''

np.where()

np.where()是NumPy的一个功能,虽然不是pandas提供的,但可以弥补df.where()的不足,所以有必要一起介绍。

df.where()方法可以将满足条件的值筛选出来,将不满足条件的值替换为另一个值,对无法满足条件的值进行替换,而np.where()就实现了这个这种功能,达到SQL中if(条件,条件为真的值,条件为假的值)的效果。

np.where()返回的是一个二维array:

# 小于60分为不及格
np.where(df>=60, '合格', '不合格')
'''
array([['合格', '不合格', '不合格', '合格'],
       ['不合格', '不合格', '不合格', '不合格'],
       ['不合格', '合格', '不合格', '合格'],
       ['合格', '合格', '合格', '合格'],
       ['合格', '不合格', '合格', '合格'],
       ....
       ['不合格', '不合格', '不合格', '不合格'],
       ['合格', '合格', '不合格', '不合格'],
       ['不合格', '合格', '不合格', '合格'],
       ['不合格', '不合格', '不合格', '合格']], dtype='<U3')
'''

可以使用 df.where()来应用它:

# 让df.where()中的条件为假,从而应用np.where()的计算结果
df.where(df==9999999, np.where(df>=60, '合格', '不合格'))
'''
       Q1     Q2     Q3     Q4
0     合格  不合格  不合格    合格
1   不合格  不合格  不合格  不合格
2   不合格    合格  不合格    合格
3     合格    合格    合格    合格
4     合格  不合格    合格    合格
..   ...    ...    ...    ...
95  不合格  不合格   合格    合格
96  不合格  不合格  不合格  不合格
97    合格    合格  不合格  不合格
98  不合格    合格  不合格    合格
99  不合格  不合格  不合格    合格

[100 rows x 4 columns]
'''

下例是np.where()对一个Series(d.avg为计算出来的虚拟列)进行判断,返回一个包含是、否结果的Series

(
    df.assign(avg=df.mean(1)) # 计算一个平均数
    # 通过np.where()及判断平均分是否及格
    .assign(及格=lambda d: np.where(d.avg>=60, '是', '否'))
)
'''
    Q1  Q2  Q3  Q4    avg 及格
0   89  21  24  64  49.50  否
1   36  37  37  57  41.75  否
2   57  60  18  84  54.75  否
3   93  96  71  78  84.50  是
4   65  49  61  86  65.25  是
..  ..  ..  ..  ..    ... ..
95  48  59  87  74  67.00  是
96  21  31  30  43  31.25  否
97  98  93   1  20  53.00  否
98  11  74  58  91  58.50  否
99  21  43  41  74  44.75  否

[100 rows x 6 columns]
'''

df.mask()

df.mask()的用法和df.where()基本相同,唯一的区别是df.mask(0将满足条件的位置填充为NaN。要对原数据表进行修改,可以加上inplace=True。

df.mask(df.列标签> 80) #如果该列标签下的单元格大于80,该列标签所在的一张航都会变成NaN

除了NaN,也可以自己指定要填充的内容

df.mask(df.列标签 > 80, ‘优秀’) #如果指定列标签下的单元格大于80,那么在返回结果中,该单元格一整行都会被替换成“优秀”

df.mask()和df.where()还可以通过数据筛选返回布尔序列:

# 返回布尔序列,符合条件的行值为True
(df.where((df.team=='A') & (df.Q1>60)) == df).Q1

# 返回布尔序列,符合条件的行值为False
(df.mask((df.team=='A') & (df.Q1>60)) == df).Q1

df.lookup()

df.lookup()的语法格式:df.lookup(行标签,列标签)

会返回一个numpy.ndarray,标签必须是一个序列

# 行列相同数量,返回一个array
df.lookup([1,3,4], ['Q1','Q2','Q3']) # array([36, 96, 61])
df.lookup([1], ['Q1']) # array([36])

6.数据迭代

收据迭代和数据遍历都是按照某种顺序逐个对数据进行访问和操作,在Python中大多由for语句来引导。

pandas的迭代操作可以将数据按行或者按列遍历,我们可以进行更加细化、个性化的数据处理。

迭代Series

Series本是是一个可迭代对象,Series df.name.values返回array结构数据可用于迭代,不过可直接对Series使用for语句来遍历它的值:

for i in df.列标签:print(i)

迭代索引和指定的多列,使用Python内置的zip函数将其打包为可迭代的zip对象:

for a,b,c in zip(df.index,df.列标签1,df.列标签2):print(a,b,c)

df.iterrows()

id.iterrows()是最常用、最方便的按行迭代方法,它可以生成一个可迭代对象,将DataFrame行作为(索引,行数据)组成的Series数据进行迭代。

在for语句中需要两个变量来承接数据:一个为索引变量,即使索引在迭代中不会使用(这种情况可用useless作为变量名);另一个为数据变量,读取具体列时,可以使用字典的方法和对象属性的方法。

for index,row in df.iterrows():

print(index,row[‘列标签1’],row.列标签2)

#打印时列标签1在原数据表中的顺序还应该排在列标签2之前

df.itertuples()

df.itertuples()生成一个namedtuples类型数据,name默认名为pandas,可以在参数中指定:

for row in df.itertuples():
    print(row)
'''
Pandas(Index=0, name='Liver', team='E', Q1=89, Q2=21, Q3=24, Q4=64)
Pandas(Index=1, name='Arry', team='C', Q1=36, Q2=37, Q3=37, Q4=57)
Pandas(Index=2, name='Ack', team='A', Q1=57, Q2=60, Q3=18, Q4=84)
Pandas(Index=3, name='Eorge', team='C', Q1=93, Q2=96, Q3=71, Q4=78)
...
'''

以下是一些使用方法实例:

# 不包含索引数据
for row in df.itertuples(index=False):
    print(row)
# Pandas(name='Liver', team='E', Q1=89, Q2=21, Q3=24, Q4=64)
# 自定义name
for row in df.itertuples(index=False, name='Gairuo'): # namedtuples
    print(row)
# Gairuo(name='Liver', team='E', Q1=89, Q2=21, Q3=24, Q4=64)

# 使用数据
for row in df.itertuples():
    print(row.Index, row.name)
'''
0 Liver
1 Arry
2 Ack
3 Eorge
4 Oah
5 Harlie
6 Acob
...
'''

df.items()

df.items()和df.iteritems()功能相同,它迭代时返回一个(列名,本列的Series结构数据),实现对列的迭代:

for a,b in df.items():

print(a)

print(b)

其中a可以遍历所有的列标签,b可以遍历所有的列的内容,末尾附加它的数据类型(Name: 列标签, dtype: object)

如果需要对Series的数据在急性迭代,可嵌套for循环。

按列迭代

除了df.items(),如需要迭代一个DataFrame的列,可以直接对DataFrame迭代,会循环得到列名:

for column in df:print(column) #遍历得到所有的列标签

再利用df[列标签]的方法得到列:

# 依次取出每个列
for column in df:
    print(df[column])

# 可对每个列的内容进行迭代
for column in df:
    for i in df[column]:
        print(i)

# 可以迭代指定列
for i in df.name:
    print(i)
# 只迭代想要的列
l = ['name', 'Q1']
cols = df.columns.intersection(l)
for col in cols:
    print (col)

与df.iterrows()相比,df.itertuples()运行速度会更快一些,推荐在数据量庞大的情况下优先使用。

迭代的优势是可以把大量重复的事物按规定的逻辑依次处理,处理逻辑部分的也能随心所欲地去发挥,同时它简单清晰,初学者也很容易理解。

7.函数应用

函数可以让复杂的常用操作模块化,既能在需要使用时直接调用,达到复用的目的,也能简化代码,pandas提供了几个常用的调用函数的方法:

  • pipe() 应用在整个DataFrame或Series上
  • apply() 应用在DataFrame的行或列中国,默认为列
  • applymap() 应用在DataFrame的每个元素中
  • map 应用在Series或DataFrame的一列的每个元素中

pipe()

pandas提供的pipe()叫做管道方法,它可以让我们写的分析过程标准化、流水线化,达到复用目标。

DataFrame和Series都支持pipe()方法。..

pipe()语法结构为df.pipe(<函数名>,<传给函数的参数列表或字典>)

它将DataFrame或Series作为函数的第一个参数,可以根据需求返回自己定义的任意数据类型。

df.pipe()的逻辑示例

pipe()可以将复杂的调用简化:

# 对df多重应用多个函数
f(g(h(df), arg1=a), arg2=b, arg3=c)

# 用pipe可以把它们连接起来
(df.pipe(h)
   .pipe(g, arg1=a)
   .pipe(f, arg2=b, arg3=c)
)

# 以下是将'arg2'参数传给函数f,然后作为函数整体接受后面的参数
(df.pipe(h)
   .pipe(g, arg1=a)
   .pipe((f, 'arg2'), arg1=a, arg3=c)
 )

函数h传入df的值返回的结果作为函数g的第一个参数值,g同时还传入了参数arg1;再将返回结果作为函数f的第一个参数,最终得到计算结果,这个调用过程显得异常复杂。使用pipe改造后代码逻辑复杂度大大降低,通过链式调用pipe()方法,对数据进行层层处理,大大提高代码的可读性。接下来我们看一下实际案例:

# 定义一个函数,给所有季度的成绩加n,然后增加平均数
# 其中n中要加的值为必传参数
def add_mean(rdf, n):
    df = rdf.copy()
    df = df.loc[:,'Q1':'Q4'].applymap(lambda x: x+n)
    df['avg'] = df.loc[:,'Q1':'Q4'].mean(1)
    return df
# 调用
df.pipe(add_mean, 100)
'''
     Q1   Q2   Q3   Q4     avg
0   189  121  124  164  149.50
1   136  137  137  157  141.75
2   157  160  118  184  154.75
3   193  196  171  178  184.50
4   165  149  161  186  165.25
..  ...  ...  ...  ...     ...
95  148  159  187  174  167.00
96  121  131  130  143  131.25
97  198  193  101  120  153.00
98  111  174  158  191  158.50
99  121  143  141  174  144.75

[100 rows x 5 columns]
'''

函数部分可以使用lambda。下例完成了一个数据筛选需求,lambda的第一个参数为self,即使用前的数据本身,后面的参数可以在逻辑代码中使用。

# 筛选出Q1大于等于80且Q2大于等于90的数据
df.pipe(lambda df_, x, y: df_[(df_.Q1 >= x) & (df_.Q2 >= y)], 80, 90)
'''
        name team  Q1  Q2  Q3  Q4
3      Eorge    C  93  96  71  78
23     Mason    D  80  96  26  49
36     Jaxon    E  88  98  19  98
97  Lincoln4    C  98  93   1  20
'''

apply()

apply()可以对DataFrame按行和列(默认)进行函数处理,也支持Series。

如果是Series,逐个传入具体值,DataFrame朱行或逐列传入:

下例中的函数使用了lambda,将文本转换为全小写:

# 将name全部变为小写
df.name.apply(lambda x: x.lower())
'''
0        liver
1         arry
2          ack
3        eorge
4          oah
        ...
95     gabriel
96     austin7
97    lincoln4
98         eli
99         ben
Name: name, Length: 100, dtype: object
'''

下面看一个DataFrame的例子。我们需要计算每个季度的平均成绩,计算方法为去掉一个最高分和一个最低分,剩余成绩的平均值为最终的平均分。

# 去掉一个最高分和一个最低分再算出平均分
def my_mean(s):
    max_min_ser = pd.Series([-s.max(), -s.min()])
    return s.append(max_min_ser).sum()/(s.count()-2)

# 对数字列应用函数
df.select_dtypes(include='number').apply(my_mean)
'''
Q1    49.193878
Q2    52.602041
Q3    52.724490
Q4    52.826531
dtype: float64
'''

分析一下代码:函数my_mean接收一个Series,从此Series中取出最大值和最小值的负值组成一个需要减去的负值Series;传入的Series追加此负值Series,最后对Series求和,求和过程中就减去了两个极值;由于去掉了两个值,分母不能取Series的长度,需要减去2,最终计算出结果。这是函数的代码逻辑。应用函数时,我们只选择数字类型的列,再使用apply调用函数my_mean,执行后,结果返回了每个季度的平均分。希望以此算法计算每个学生的平均成绩,在apply中传入axis=1则每行的数据组成一个Series传入自定义函数中。

# 同样的算法以学生为维度计算
(
    df.set_index('name') # 设定name为索引
    .select_dtypes(include='number')
    .apply(my_mean, axis=1) # 横向计算
)

'''
name
Liver       44.0
Arry        37.0
Ack         58.5
Eorge       85.5
Oah         63.0
            ...
Gabriel     66.5
Austin7     30.5
Lincoln4    56.5
Eli         66.0
Ben         42.0
Length: 100, dtype: float64
'''

由上面的案例可见,直接调用lambda函数非常方便。在今后的数据处理中,我们会经常使用这种操作。以下是一个判断一列数据是否包含在另一列数据中的案例。

# 判断一个值是否在另一个类似列表的列中
df.apply(lambda d: d.s in d.s_list, axis=1) # 布尔序列
df.apply(lambda d: d.s in d.s_list, axis=1).astype(int) # 0 和 1 序列

它常被用来与NumPy库中的np.where()方法配合使用,如下例:

# 函数,将大于90分数标记为good
fun = lambda x: np.where(x.team=='A' and x.Q1>90, 'good' ,'other')
df.apply(fun, axis=1)
# 同上效果
(df.apply(lambda x: x.team=='A' and x.Q1>90, axis=1)
 .map({True:'good', False:'other'})
)
df.apply(lambda x: 'good' if x.team=='A' and x.Q1>90 else '', axis=1)

总结一下,apply()可以应用的函数类型如下:

df.apply(fun) # 自定义
df.apply(max) # Python内置函数
df.apply(lambda x: x*2) # lambda
df.apply(np.mean) # NumPy等其他库的函数
df.apply(pd.Series.first_valid_index) # Pandas自己的函数

后面介绍到的其他调用函数的方法也适用这个规则。

applymap()

df.applymap()可实现元素级函数应用,即对DataFrame中所有的元素(不包含索引)应用函数处理

使用lambda时,变量是指每一个具体的值。

# 计算数据的长度
def mylen(x):
    return len(str(x))

df.applymap(lambda x:mylen(x)) # 应用函数
df.applymap(mylen) # 效果同上
'''
    name  team  Q1  Q2  Q3  Q4
0      5     1   2   2   2   2
1      4     1   2   2   2   2
2      3     1   2   2   2   2
3      5     1   2   2   2   2
4      3     1   2   2   2   2
..   ...   ...  ..  ..  ..  ..
95     7     1   2   2   2   2
96     7     1   2   2   2   2
97     8     1   2   2   1   2
98     3     1   2   2   2   2
99     3     1   2   2   2   2

[100 rows x 6 columns]
'''

map()

map()根据输入对应关系映射值返回最终数据,用于Series对象或DataFrame对象的一列。传入的值可以是一个字典,键为原始数据值,值为替换后的值。可以穿入一个函数(参数为Series的每个值),还可以传入一个字符格式化表达式来格式化数据内容。

df.team.map({'A':'一班', 'B':'二班','C':'三班', 'D':'四班',}) # 枚举替换
df.team.map('I am a {}'.format)
df.team.map('I am a {}'.format, na_action='ignore')
t = pd.Series({'six': 6., 'seven': 7.})
s.map(t)
# 应用函数
def f(x):
    return len(str(x))

df['name'].map(f)

agg()

agg()一般用于使用指定轴上的 一项或多项操作进行汇总,可以传入一个函数或函数的字符,还可以用列表的形式传入多个函数。

# 每列的最大值
df.agg('max')
# 将所有列聚合产生sum和min两行
df.agg(['sum', 'min'])
# 序列多个聚合
df.agg({'Q1' : ['sum', 'min'], 'Q2' : ['min', 'max']})
# 分组后聚合
df.groupby('team').agg('max')
df.Q1.agg(['sum', 'mean'])

def mymean(x):
    return x.mean()

df.Q2.agg(['sum', mymean])

另外,agg()还支持传入函数的位置参数和关键字参数,支持每个列分别用不同的方法聚合,支持指定轴的方向。

# 每列使用不同的方法进行聚合
df.agg(a=('Q1', max),
       b=('Q2', 'min'),
       c=('Q3', np.mean),
       d=('Q4', lambda s:s.sum()+1)
      )
# 按行聚合
df.loc[:,'Q1':].agg("mean", axis="columns")
# 利用pd.Series.add方法对所有数据加分,other是add方法的参数
df.loc[:,'Q1':].agg(pd.Series.add, other=10)

agg()的用法整体上与apply()极为相似。

transform

DataFrame或Series自身调用函数并返回一个与自身长度相同的数据。

df.transform(lambda x: x*2) # 应用匿名函数
df.transform([np.sqrt, np.exp]) # 调用多个函数
df.transform([np.abs, lambda x: x + 1])
df.transform({'A': np.abs, 'B': lambda x: x + 1})
df.transform('abs')
df.transform(lambda x: x.abs())

可以对比下面两个操作:

df.groupby('team').sum()
'''
        Q1    Q2    Q3    Q4
team
A     1066   639   875   783
B      975  1218  1202  1136
C     1056  1194  1068  1127
D      860  1191  1241  1199
E      963  1013   881  1033
'''

df.groupby('team').transform(sum)
'''
                                                 name    Q1    Q2    Q3    Q4
0   LiverJamesMaxIsaacTeddyRileyJosephJaxonArlo8Ju...   963  1013   881  1033
1   ArryEorgeHarlieArchieTheoWilliamDanielAlexande...  1056  1194  1068  1127
2   AckLfieOscarJoshuaHenryLucasArthurReggie1TobyD...  1066   639   875   783
3   ArryEorgeHarlieArchieTheoWilliamDanielAlexande...  1056  1194  1068  1127
4   OahReddieEthanMasonFinleyBenjaminLouieCarter7B...   860  1191  1241  1199
..                                                ...   ...   ...   ...   ...
95  ArryEorgeHarlieArchieTheoWilliamDanielAlexande...  1056  1194  1068  1127
96  ArryEorgeHarlieArchieTheoWilliamDanielAlexande...  1056  1194  1068  1127
97  ArryEorgeHarlieArchieTheoWilliamDanielAlexande...  1056  1194  1068  1127
98  LiverJamesMaxIsaacTeddyRileyJosephJaxonArlo8Ju...   963  1013   881  1033
99  LiverJamesMaxIsaacTeddyRileyJosephJaxonArlo8Ju...   963  1013   881  1033

[100 rows x 5 columns]
'''

分组后,直接使用计算函数并安分组显示合计数据。使用transform()调用计算函数,返回的是原数据的结构,但在指定位置上显示聚合计算后的结果,这样方便我们了解数据所在组的情况。

copy()

类似于Python中的copy()函数,df.copy()方法可以返回一个新对象,这个新对象与原对象没有关系。

当deep=True(默认)时,将创建一个新对象,其中包含调用对象的数据和索引的副本。对副本数据或索引的修改不会反应在原始对象中。

当deep=False时,换创建一个新对象而不复制调用对象的数据或索引(仅复制对数据和索引的引用)。原始数据的任何更改都将对浅拷贝的副本进行同步更改,反之亦然。

s = pd.Series([1, 2], index=["a", "b"])
s_1 = s
s_copy = s.copy()
s_1 is s # True
s_copy is s # False

发表回复