Pandas基础(一):单级索引、多级索引、索引设定、索引型函数、抽样
DataWhale第十二期组队学习:python Pandas
本文对应教程及数据表获取:joyful-pandas
# 导入相关的依赖包
import pandas as pd
import numpy as np
pd.__version__ # 1.0.3
# 本文对应的数据表示例
df = pd.read_csv('data/table.csv',index_col='ID')
df.head(10)
School | Class | Gender | Address | Height | Weight | Math | Physics | |
---|---|---|---|---|---|---|---|---|
ID | ||||||||
1101 | S_1 | C_1 | M | street_1 | 173 | 63 | 34.0 | A+ |
1102 | S_1 | C_1 | F | street_2 | 192 | 73 | 32.5 | B+ |
1103 | S_1 | C_1 | M | street_2 | 186 | 82 | 87.2 | B+ |
1104 | S_1 | C_1 | F | street_2 | 167 | 81 | 80.4 | B- |
1105 | S_1 | C_1 | F | street_4 | 159 | 64 | 84.8 | B+ |
1201 | S_1 | C_2 | M | street_5 | 188 | 68 | 97.0 | A- |
1202 | S_1 | C_2 | F | street_4 | 176 | 94 | 63.5 | B- |
1203 | S_1 | C_2 | M | street_6 | 160 | 53 | 58.8 | A+ |
1204 | S_1 | C_2 | F | street_5 | 162 | 63 | 33.8 | B |
1205 | S_1 | C_2 | F | street_6 | 167 | 63 | 68.4 | B- |
单级索引有三种常用的方式.loc
、.iloc
、[]
,分别表示标签索引、位置索引、切片操作
在loc
中使用切片全部包含右端点
"""行索引"""
# 单行索引
df.loc[1103] #选中index为1103的行
# 多行索引
df.loc[[1102,2304]]
df.loc[1304:]
df.loc[2402::-1]
"""列索引"""
# 单列索引
df.loc[:,'Height'] # 选中列名为Height的所有行
# 多列索引
df.loc[:,['Height','Math']] # 用列表表示两列
df.loc[:,'Height':'Math'] # 用切片表示三列
"""同时限定行列"""
# 行列索引
df.loc[1102:2401:3,'Height':'Math']
"""通过函数索引"""
# 函数式索引
df.loc[lambda x:x['Gender']=='M']
# 函数式索引-2
def f(x):
return [1101,1103]
df.loc[f]
"""通过布尔变量索引"""
# 布尔索引
# []中是一个值为布尔类型的Series
df.loc[df['Address'].isin(['street_7','street_4'])]
# []中是一个值为布尔类型的List
df.loc[[True if i[-1]=='4' or i[-1]=='7' else False for i in df['Address'].values]]
与loc
不同,切片右端点不包含,且iloc
是通过位置而不是通过标签来索引的
例如小明同学排在队伍第五个。那loc
就是小明同学出列,而iloc
就是第五位同学出列
"""行索引"""
# 单行索引
df.iloc[3] # 等同于df.loc[1104]
# 多行索引
df.iloc[3:5] # 等同于df.loc[[1104,1105]]
"""列索引"""
# 单列索引
df.iloc[:,3]
# 多列索引
df.iloc[:,7::-2]
"""行列同时索引"""
# 行列索引
df.iloc[3::4,7::-2]
"""函数式索引"""
# 通过函数进行索引
# 表达式中[3]返回DataFrame,3 返回Series,内容一样
df.iloc[lambda x:[3]].head()
因为iloc
中的参数只能为整数或整数列表,所以不能使用布尔索引
在行索引为浮点数的时候不要使用[]操作符,因为在Series中的浮点[]并不是位置比较,而是值比较
# 提取一列作为Series
s = pd.Series(df['Math'],index=df.index)
"""行索引"""
# 单行索引对于Series其实也就是单元素
s[1101] # 使用的是索引标签
# 多行索引
s[0:4] # 使用的是绝对位置的整数切片
"""函数式索引"""
# 函数式索引
s[lambda x: x.index[16::6]]
"""布尔索引"""
# []内是一个值为布尔类型的Series
s[s>80]
"""行索引"""
# 绝对位置切片
df[1:2] # 取出1102这一行
df[3:5] # 多行索引
# 元素索引
row = df.index.get_loc(1102)
df[row:row+1] # 效果同上
"""列索引"""
# 单列索引
df['School']
# 多列索引
df[['School','Math']] # 传入一个列表
"""函数式索引"""
df[lambda x:['Math','Physics']]
"""布尔索引"""
# 传入的依然是一个Series
df[df['Gender']=='F']
"""利用与或非进行索引"""
df[(df['Gender']=='F')&(df['Address']=='street_2')]
df[(df['Math']>85)|(df['Address']=='street_7')]
df[~((df['Math']>75)|(df['Address']=='street_1'))]
"""在loc的相应位置中使用布尔列表选择"""
# 这里其实本质上是一个loc的行列索引
df_new = df.loc[df['Math']>60,(df[:8]['Address']=='street_6').values]
"""isin方法"""
# 包含返回True
df[df['Address'].isin(['street_1','street_4'])&df['Physics'].isin(['A','A+'])]
# all的作用等同“与”,1代表跨列方向
df[ df[['Address','Physics']].isin({
'Address':['street_1','street_4'],'Physics':['A','A+']}).all(1)]
当我们只要一个元素的时候,at
和iat
方法能够提供更快的速度
display(df.at[1101,'School'])
display(df.loc[1101,'School'])
display(df.iat[0,0])
display(df.iloc[0,0])
%timeit df.at[1101,'School'] # 6.24us
%timeit df.loc[1101,'School'] # 10.9us
%timeit df.iat[0,0] # 6.85us
%timeit df.iloc[0,0] # 11.2us
"""利用interval_range方法"""
# 分出(0, 1], (1, 2], (2, 3], (3, 4], (4, 5]五个区间
pd.interval_range(start=0,end=5) # closed参数控制左右闭合,默认左开右闭
# 分出步长为5的8个区间,(0, 5], (5, 10],……
pd.interval_range(start=0,periods=8,freq=5)
"""利用cut将数值类型的列变为区间"""
# 所有Math数值都变为形如(0, 40],(80, 100]的样子
# 结果为一个Series
math_interval = pd.cut(df['Math'],bins=[0,40,60,80,100])
"""对数值类型列可以区间索引"""
# 先在右侧添加一个新的数学成绩区间列
# 然后选出数学和数学成绩组成新df,再重设index为成绩区间
df_i = df.join(math_interval,rsuffix='_interval')[['Math','Math_interval']]\
.reset_index().set_index('Math_interval')
# 包含该值就会被选中
df_i.loc[65] # 结果为一个DataFrame
df_i.loc[[65,90]]
# 若我要选出某个成绩区间,需要把分类变量转为区间变量
df_i[df_i.index.astype('interval').overlaps(pd.Interval(70, 85))].head()
# 直接创建元组
# 第一级为Upper:A,B,第二级为Lower:a,b,a,b
tuples = [('A','a'),('A','b'),('B','a'),('B','b')]
mul_index = pd.MultiIndex.from_tuples(tuples, names=('Upper', 'Lower'))
# 通过值与多级索引创建DataFrame
pd.DataFrame({
'Score':['perfect','good','fair','bad']},index=mul_index)
# 利用zip创建元组
L1 = list('AABB')
L2 = list('abab')
tuples = list(zip(L1,L2))
mul_index = pd.MultiIndex.from_tuples(tuples, names=('Upper', 'Lower'))
pd.DataFrame({
'Score':['perfect','good','fair','bad']},index=mul_index)
# 通过Array创建
# arrays自动转化为元组
arrays = [['A','a'],['A','b'],['B','a'],['B','b']]
mul_index = pd.MultiIndex.from_tuples(arrays, names=('Upper', 'Lower'))
pd.DataFrame({
'Score':['perfect','good','fair','bad']},index=mul_index)
# 通过交叉乘积创建
L1 = ['A','B']
L2 = ['a','b']
pd.MultiIndex.from_product([L1,L2],names=('Upper', 'Lower'))
# 指定原有df中的列创建
df_using_mul = df.set_index(['Class','Address'])
# 一般切片
df_using_mul.sort_index().index.is_lexsorted() # 检查是否排序
df_using_mul.sort_index().loc['C_2','street_5'] # 排序后切片
df_using_mul.sort_index().loc[('C_2','street_6'):('C_3','street_4')]
df_using_mul.sort_index().loc[('C_2','street_7'):'C_3']
# 通过元组构成列表
# 选出满足这二种组合的所有行
df_using_mul.sort_index().loc[[('C_2','street_7'),('C_3','street_2')]]
# 通过列表构成元组
# 选出满足这四种组合的所有行
df_using_mul.sort_index().loc[(['C_2','C_3'],['street_4','street_7']),:]
# 行列各9种组合
L1,L2 = ['A','B','C'],['a','b','c']
mul_index1 = pd.MultiIndex.from_product([L1,L2],names=('Upper', 'Lower'))
L3,L4 = ['D','E','F'],['d','e','f']
mul_index2 = pd.MultiIndex.from_product([L3,L4],names=('Big', 'Small'))
df_s = pd.DataFrame(np.random.rand(9,9),index=mul_index1,columns=mul_index2)
# 使用索引Slice
idx=pd.IndexSlice
# 选出从B开始指定列大于0.3的行
# 选出列之和大于4的列
df_s.loc[idx['B':,df_s['D']['d']>0.3],idx[df_s.sum()>4]]
# swaplevel方法(两层交换)
df_using_mul.swaplevel(i=1,j=0,axis=0).sort_index()
# reorder_levels方法(多层交换)
# 数字大小对应的是原来的位置
# 数字位置对应的是改变的位置
df_muls = df.set_index(['School','Class','Address'])
df_muls.reorder_levels([2,0,1],axis=0).sort_index()
# 不用数字也可以直接用Name
df_muls.reorder_levels(['Address','School','Class'],axis=0).sort_index()
# index_col参数
# 设定索引列为Address、School
pd.read_csv('data/table.csv',index_col=['Address','School'])
# reindex和reindex_like
# 新表只有这四行,且1206这一行为NaN,因为原表没有1206
df.reindex(index=[1101,1203,1206,2402])
# 新表只有这三列,同理Average这一列为NaN
df.reindex(columns=['Height','Gender','Average'])
# 接上面,对缺失值填充
'''
bfill表示用所在索引1206的后一个有效行填充
ffill为前一个有效行
nearest是指最近的
'''
df.reindex(index=[1101,1203,1206,2402],method='bfill')
df.reindex(index=[1101,1203,1206,2402],method='nearest')
# reindex_like
# 生成一个横纵索引完全与参数列表一致的DataFrame,数据使用被调用的表
# 数据为df_temp的值
df_temp = pd.DataFrame({
'Weight':np.ones(5),
'Height':np.ones(5),
'ID':[1101,1104,1103,1105,1102]}).set_index('ID')
df_temp.reindex_like(df[0:5][['Weight','Height']])
# 新表中的1105是通过bfill填充的1106的值
df_temp = pd.DataFrame({
'Weight':range(5),
'Height':range(5),
'ID':[1101,1104,1103,1106,1102]}).set_index('ID').sort_index()
df_temp.reindex_like(df[0:5][['Weight','Height']],method='bfill')
# 使用表内列作为索引
df.set_index('Class')
# 在原来index基础上添加(右侧)
df.set_index('Class',append=True)
# 当使用与表长相同的列作为索引(先转化为Series)
df.set_index(pd.Series(range(df.shape[0])))
# 直接添加多级索引
df.set_index([pd.Series(range(df.shape[0])),pd.Series(np.ones(df.shape[0]))])
"""reset_index方法,主要是将索引重置"""
# 恢复到自然数索引
df.reset_index()
# level指定哪一层被reset,col_level指定set到哪一层
# 第二级行索引被重置,重置为列,级别为第二级列名
df_temp.reset_index(level=1,col_level=1)
# rename_axis修改某一层的索引名,而不是索引标签
df_temp.rename_axis(index={
'Lower':'LowerLower'},columns={
'Big':'BigBig'})
# rename方法用于修改列或者行索引标签,而不是索引名
df_temp.rename(index={
'A':'T'},columns={
'e':'changed_e'})
# 不满足条件的行全部被设置为NaN
df.where(df['Gender']=='M')
# 丢弃掉为NaN的行
df.where(df['Gender']=='M').dropna()
# 不丢弃,填充进去
df.where(df['Gender']=='M',np.random.rand(df.shape[0],df.shape[1]))
# 与上一个相反,满足条件的扔了
df.mask(df['Gender']=='M').dropna().head()
# 不丢弃,填充进去
df.mask(df['Gender']=='M',np.random.rand(df.shape[0],df.shape[1]))
# 利用布尔表达式查找
# 合法符号:行列索引名、字符串、and/not/or/&/|/~/not in/in/==/!=、四则运算符
df.query('(Address in ["street_6","street_7"])&(Weight>(70+10))&(ID in [1303,2304,2402])')
# 返回Class值下是否重复的布尔列表
df.duplicated('Class')
# keep参数下的duplicated
# 默认为first,即首次出现设为不重复(False)
# 若为last,则最后一次设为不重复(False)
# 若为False,则所有重复项为重复(True)
df.duplicated('Class',keep='last')
df.duplicated('Class',keep=False)
# 剔除重复项,默认保留第一个
df.drop_duplicates('Class')
# 剔除重复项,保留最后一个
df.drop_duplicates('Class',keep='last')
# 将多列共同视作一个多级索引(一个组合),比较重复项
df.drop_duplicates(['School','Class'])
抽样函数指的就是
sample
函数
# 随机抽取5个样本
df.sample(n=5)
# 按比例抽取样本
df.sample(frac=0.05)
# 是否允许一行可以采样多次
df.sample(n=df.shape[0],replace=True)
# axis选择要抽样的坐标轴,n为数量
df.sample(n=3,axis=1) # 随机抽取3列
# weights为样本权重,自动归一化
df.sample(n=3,weights=np.random.rand(df.shape[0]))
# 以某一列为权重,数学分高的容易抽中
df.sample(n=10,weights=df['Math'])
【问题一】 如何更改列或行的顺序?如何交换奇偶行(列)的顺序?
可以使用pandas.DataFrame.reindex
改变顺序
奇偶行主要是设定参数
[rv for r in zip(b,a) for rv in r]
形成一个新的改变顺序的列表【问题二】 如果要选出DataFrame的某个子集,请给出尽可能多的方法实现。
行索引、列索引、函数索引、布尔索引、区间索引、快速标量索引、索引型函数、抽样函数
【问题三】 query函数比其他索引方法的速度更慢吗?在什么场合使用什么索引最高效?
按照正常行列索引会慢一点。如果逻辑比较的表达式比较复杂时用query更方便
【问题四】 单级索引能使用Slice对象吗?能的话怎么使用,请给出一个例子
idx=pd.IndexSlice
df.loc[:,idx['Address':]]
【问题五】 如何快速找出某一列的缺失值所在索引?
pandas.Series.isna
,返回一个大小一样的Series
,包含值为布尔变量,表明是否为NaN
【问题六】 索引设定中的所有方法分别适用于哪些场合?怎么直接把某个DataFrame的索引换成任意给定同长度的索引?
index_col
:读取的时候设定多级索引reindex和reindex_like
:重新排序、按照已有dataFrame创建set_index和reset_index
:重新设定索引、重置某个索引rename_axis和rename
:重置索引名、重置索引标签【问题七】 多级索引有什么适用场合?
一份数据含有多个特征,且特征也有递进或者包含的关系
【问题八】 什么时候需要重复元素处理
选出第一个(最后一个)不重复元素、判断数据的总共种类有多少、清洗数据、剔除重复项
df1 = pd.read_csv('data/UFO.csv')
df1.head()
(a)在所有被观测时间超过60s的时间中,哪个形状最多?
df1[df1['duration (seconds)']>60]['shape'].value_counts().index[0] # light
(b)对经纬度进行划分:-180°至180°以30°为一个划分,-90°至90°以18°为一个划分,请问哪个区域中报告的UFO事件数量最多?
longitude_interval = pd.cut(df1['longitude'],bins=pd.interval_range(start=-180,end=180,freq=30))
latitude_interval = pd.cut(df1['latitude'],bins=pd.interval_range(start=-90,end=90,freq=18))
df_ll = df1.join(latitude_interval,rsuffix='_interval').join(longitude_interval,rsuffix='_interval')
df_ll.set_index(['latitude_interval','longitude_interval']).index.value_counts()
# ((36, 54], (-90, -60]) 27891
df2 = pd.read_csv('data/Pokemon.csv')
df2.head()
(a)双属性的Pokemon占总体比例的多少?
df2['Type 2'].count()/df2.shape[0] # 0.5175
(b)在所有种族值(Total)不小于580的Pokemon中,非神兽(Legendary=False)的比例为多少?
df2[df2['Total']>=580]['Legendary'].value_counts(normalize='True')
# True 0.575221
# False 0.424779
(c)在第一属性为格斗系(Fighting)的Pokemon中,物攻排名前三高的是哪些?
df2[df2['Type 1']=='Fighting'].nlargest(3,'Attack')
# LucarioMega Lucario
# Conkeldurr
# Machamp
(d)请问六项种族指标(HP、物攻、特攻、物防、特防、速度)极差的均值最大的是哪个属性(只考虑第一属性,且均值是对属性而言)?
df2['range'] = df2.iloc[:,5:11].max(axis=1)-df2.iloc[:,5:11].min(axis=1)
attribute = df2[['Type 1','range']].set_index('Type 1')
max_range = 0
result = ''
for i in attribute.index.unique():
temp = attribute.loc[i,:].mean()
if temp.values[0] > max_range:
max_range = temp.values[0]
result = i
result
# Steel
(e)哪个属性(只考虑第一属性)的神兽占所有神兽比例最高?该属性神兽的种族值也是最高的吗?
df2.query('Legendary == True')['Type 1'].value_counts().index[0] # Psychic
attribute = df2.query('Legendary == True')[['Type 1','Total']].set_index('Type 1')
max_value = 0
result = ''
for i in attribute.index.unique()[:-1]:
temp = attribute.loc[i,:].mean()
if temp[0] > max_value:
max_value = temp[0]
result = i
result
# Normal