数据分析索引总结(下)Pandas索引技巧

2020-06-04 11:57:22 浏览数 (1)

作者:闫钟峰,Datawhale优秀学习者

寄语:本文对索引设定、常用索引型函数、重复元素处理、抽样函数等内容做了详细介绍。

索引设定

1. index_col参数

index_col是read_csv中的一个参数,而不是某一个方法,在使用 read_csv 函数读取文本的时候使用index_col参数指定用哪几个列作为索引
代码语言:javascript复制
pd.read_csv('data/table.csv',index_col=['Address','School']).head()

2. reindex和reindex_like

reindex是指重新索引,它的重要特性在于索引对齐,很多时候用于重新排序。
代码语言:javascript复制
df.head()
通过为reindex参数指定一个新的list,使得原始df的行重新排列。
代码语言:javascript复制
df.reindex(index=sorted(list(df.index),reverse=True)).head()
如果传入的list是原始index的一个真子集, 则会实现分层抽样的效果
代码语言:javascript复制
df.reindex(index=list(df.index)[::5])
为index传入的参数可以不是df的原始index中的值---这将引入缺失值构成的行,还可以传入重复索引。
代码语言:javascript复制
df.reindex(index=[1101,1101,1203,1206,2402])
为columns传入参数,将对列索引做类似的事情。
代码语言:javascript复制
df.reindex(columns=['Height','Height','Gender','Average']).head()
同时为index和columns传入原始index和columns 的真子集, 同样可以实现获取原始df的子df的目的。由于reindex的copy参数默认值是True,这时会返回一个新的变量,而不是修改原始df。
代码语言:javascript复制
df.reindex(index=[1101,1203,2402],columns=['Height','Gender'])
可以选择缺失值的填充方法:
fill_value和method(bfill/ffill/nearest),其中method参数必须索引单调。bfill表示用所在索引1206的后一个有效行填充,ffill为前一个有效行。
代码语言:javascript复制
df.reindex(index=[1101,1203,1206,2402],method='bfill')
数值上1205比1301更接近1206,因此用前者填充。nearest的最近, 是指在原始df中离得最近的,这个离得最近的可能并没有在被 reindex 的时候选中。
代码语言:javascript复制
df.reindex(index=[1101,1203,1206,2402,1205,1301],method='nearest')
reindex_like的作用为生成一个横纵索引完全与参数列表一致的DataFrame,数据使用被调用的表。
代码语言:javascript复制
df_temp = pd.DataFrame({'Weight':np.zeros(5),
                        'Height':np.zeros(5),
                        'ID':[1101,1104,1103,1106,1102]}).set_index('ID')
表中的值数据来自于df_temp, 而行索引和列索引则来自于传入的 df[0:5][['Weight','Height']]。由于df_temp中没有1105这个行索引,因此会引入缺失值。
代码语言:javascript复制
df_temp.reindex_like(df[0:5][['Weight','Height']])
使用reindex方法实现上述reindex_like的效果--reindex_like可以看作是该方法的语法糖。
代码语言:javascript复制
df_temp.reindex(index=df[0:5][['Weight','Height']].index,columns=df[0:5][['Weight','Height']].columns)
如果df_temp单调还可以使用method参数。
代码语言:javascript复制
df_temp = pd.DataFrame({'Weight':range(5),
                        'Height':range(5),
                        'ID':[1101,1104,1103,1106,1102]}).set_index('ID').sort_index()
可以自行检验这里的1105的值是否是由bfill规则填充
代码语言:javascript复制
df_temp.reindex_like(df[0:5][['Weight','Height']],method='bfill')
注意是用1105这个索引在df.temp中的前边或后边的值进行填充,
代码语言:javascript复制
df_temp.reindex_like(df[0:5][['Weight','Height']],method='ffill')
使用reindex实现相同的效果。
代码语言:javascript复制
df_temp.reindex(index=df[0:5][['Weight','Height']].index,columns=df[0:5][['Weight','Height']].columns,method='ffill')

3. set_index和reset_index

先介绍set_index:从字面意思看,就是将某些列作为索引。使用表内列作为索引:
代码语言:javascript复制
df.head()
将df的列设置为索引, 参数 drop 默认丢弃原来的索引。
代码语言:javascript复制
df.set_index('Class').head()
利用append参数可以将当前索引维持不变
这种情况下会把新的索引作为次级索引。
代码语言:javascript复制
df.set_index('Class',append=True).head()
使用与表长相同的列作为索引(需要先转化为Series,否则报错)
代码语言:javascript复制
df.set_index(pd.Series(range(df.shape[0]))).head()
如果恰好列名是用的默认整数索引, 并且包含了传入的参数,是否这些列会被设置成索引?
代码语言:javascript复制
df_=pd.DataFrame(np.random.randn(24).reshape((3,8)))
df_.set_index(list(range(df_.shape[0])))
# 传入参数是 range(df_.shape[0] 时会报错:
# KeyError: 'None of [range(0, 3)] are in the columns'
# 当给 set_index 传入的是list的时候, 就会把列名和list一致的列设置为索引
看参数说明,并不一定需要Series
代码语言:javascript复制
df.set_index(np.arange(df.shape[0])).head()
可以直接添加多级索引:
传入由多个类似 Series 的元素构成的list, 就会用这个多个类Series的元素作为多级索引。
代码语言:javascript复制
df.set_index([pd.Series(range(df.shape[0])),pd.Series(np.ones(df.shape[0]))]).head()
下面介绍reset_index方法,它的主要功能是将索引重置为df的列
默认状态直接恢复到自然数索引:
代码语言:javascript复制
df.reset_index().head()
多级索引时用level参数指定哪一层被reset,用col_level参数指定将索引名称set到多重列索引的哪一层:
代码语言:javascript复制
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_temp = pd.DataFrame(np.random.rand(9,9),index=mul_index1,columns=mul_index2)
将编号为1的行索引(也就是次级索引)重置为列, 原来的次级索引名作为列索引的编号为0(也就是列索引的顶级索引),这时该列的次级列索引为空。
代码语言:javascript复制
df_temp1 = df_temp.reset_index(level=1,col_level=0)
移出最内层索引
代码语言:javascript复制
df_temp1.index

4. rename_axis和rename

rename_axis是针对多级索引的方法,作用是修改某一层索引的索引名(index.name),而不是索引的索引值(索引标签)
这里为index和columns传入的均是一个字典,键为原来的索引名称,值为新的索引名称。
代码语言:javascript复制
df_temp.rename_axis(index={'Lower':'LowerLower'},columns={'Big':'BigBig'})
还可以直接使用赋值语句修改或命名索引--当原始索引并没有名字的时候,这种方式更加便捷。
代码语言:javascript复制
df_temp.index.names=['UPPER','LOWER']
代码语言:javascript复制
dftemp=pd.DataFrame(np.random.randn(20).reshape(10,2), index=pd.MultiIndex.from_tuples(list(np.random.randint(1,5,30).reshape(-1,3)))).sort_index()
由于索引的name为空,因此使用rename_index无法将其重命名。
代码语言:javascript复制
dftemp.rename_axis(index={0:'LEFT',2:'RIGHT'},)
但是用赋值语句可以修改各层级索引的名字---能否只修改某一层级的索引的名字?
代码语言:javascript复制
dftemp.index.names=['LEFT','MIDDLE','RIGHT']
重置dftemp
代码语言:javascript复制
dftemp=pd.DataFrame(np.random.randn(20).reshape(10,2), index=pd.MultiIndex.from_tuples(list(np.random.randint(1,5,30).reshape(-1,3)))).sort_index()
但是用赋值语句可以修改各层级索引的名字---可以只修改某一层级的索引的名字?
代码语言:javascript复制
# 传入一个和索引层级等长的list, 不需要命名的层级赋值 None, 需要命名的层级传入字符串
dftemp.index.names=[None,None,'RIGHT']
当然非层次化索引也可以用rename_axis重命名索引名。
代码语言:javascript复制
df_temp1.rename_axis(index={'Upper':'UPPER'})
rename方法用于修改列或者行索引标签,而不是索引名

给index传入的字典,键是原来的索引值, 值是新的索引值。无需指定要修改的索引级别,会自动寻找索引中的相应的值----当不同层级的索引有相同的值的时候,这会造成混乱。

代码语言:javascript复制
df_temp.rename(index={'A':'T'},columns={'e':'changed_e'}).head()
当不同层级的索引有相同的值的时候,这会造成混乱。
代码语言:javascript复制
L1,L2 = ['A','B','C'],['a','b','c']
mul_index1 = pd.MultiIndex.from_product([L1,L1],names=('Upper1', 'Upper2'))
L3,L4 = ['D','E','F'],['d','e','f']
mul_index2 = pd.MultiIndex.from_product([L3,L4],names=('Big', 'Small'))
df_t = pd.DataFrame(np.random.rand(9,9),index=mul_index1,columns=mul_index2)
当不同层级的索引有相同的值的时候,这会造成混乱--注意行索引的每一层的 A 都被改为了 T。
代码语言:javascript复制
df_t.rename(index={'A':'T'},columns={'e':'changed_e'}).head()
代码语言:javascript复制
# 如果要同时修改行索引的第二层,以及列索引的第一层,怎么指定level?
代码语言:javascript复制
df_t.rename(index={'A':'T'},level=1,columns={'E':'changed_e'},level=0).head()
# 显然不能同时指定两个level
一个问题:如果不同层级的索引的索引值有相同的值的时候,要想修改特定级别的索引的索引值(比如次级索引中的A,修改为a),需要如何修改?
答:使用参数level=1指定需要修改的层级为次级索引
代码语言:javascript复制
df_t.rename(index={'A':'T'},columns={'e':'changed_e'},level=1).head()

常用索引型函数

1. where函数

当对条件为False的单元进行填充,不满足条件的行全部被设置为NaN
代码语言:javascript复制
df.where(df['Gender']=='M').head()
注意和query的区别
代码语言:javascript复制
df.query('Gender=="M"').head()
where函数需要增加一个dropna()才会和query等价,通过这种方法筛选结果和[ ]操作符的结果完全一致。
代码语言:javascript复制
df.where(df['Gender']=='M').dropna().head()
[] 操作符--实际上是传入了布尔值索引。
代码语言:javascript复制
df[df['Gender']=='M'].head()
第一个参数con为布尔条件,第二个参数other为填充值
代码语言:javascript复制
df.where(df['Gender']=='M',np.random.rand(df.shape[0],df.shape[1])).head()

2. mask函数

mask函数与where功能上相反,其余完全一致,即对条件为True的单元进行填充
代码语言:javascript复制
df.mask(df['Gender']!='M').dropna().head()
df.mask(df['Gender']!='M',np.random.rand(df.shape[0],df.shape[1])).head()

3. query函数

query函数中的布尔表达式中,下面的符号都是合法的:行列索引名、字符串、and / not / or / & / | / ~ / not in / in /==/!=、四则运算符。
代码语言:javascript复制
df.query('(Address in ["street_6","street_7"])&(Weight>(70 10))&(ID in [1303,2304,2402])')
实际上就是根据列值满足的条件筛选行---基本上和sql里的where子句对应。注意传入的参数是带引号的

重复元素处理

1. duplicated方法

该方法返回了是否重复的布尔列表。
代码语言:javascript复制
df.duplicated('Class').head()
可选参数keep默认为first,即首次出现设为不重复,若为last,则最后一次设为不重复,若为False,则所有重复项为False。
代码语言:javascript复制
df.duplicated('Class',keep='last').tail()
df.duplicated('Class',keep=False).head() # - False : Mark all duplicates as ``True``.

2. drop_duplicates方法

从名字上看出为剔除重复项,这在后面章节中的分组操作中可能是有用的,例如需要保留每组的第一个值:
代码语言:javascript复制
df.drop_duplicates('Class')
上边有些类似于mysql中按某列groupby之后,还能选择其他的非分组列。
代码语言:javascript复制
df.drop_duplicates('Class',keep='last')
在传入多列时等价于将多列共同视作一个多级索引,比较重复项:
代码语言:javascript复制
df.drop_duplicates(['School','Class'])

抽样函数

这里的抽样函数指的就是sample函数

1. n为样本量

代码语言:javascript复制
df.sample(n=5)
df.sample(9)#由于是第一个参数,可以省略 n=

2. frac为抽样比

代码语言:javascript复制
df.sample(frac=0.05)

3. replace为是否放回

代码语言:javascript复制
df.sample(n=df.shape[0],replace=True).head()
# 有放回(replace=True)可以选择比df长度更多的元素回来
len(df.sample(n=123,replace=True)),len(df)
df.sample(n=35,replace=True).index.is_unique

4. axis为抽样维度,默认为0,即抽行

代码语言:javascript复制
# axis=1 对列进行抽样
df.sample(n=3,axis=1).head()

5. weights为样本权重,自动归一化

代码语言:javascript复制
w=np.random.rand(df.shape[0])
df.sample(n=3,weights=w).head()

以某一列为权重,这在抽样理论中很常见

代码语言:javascript复制
df.sample(n=3,weights=df['Math']).head()

以某一列为权重,这在抽样理论中很常见--本质上是以这一列的列值为权重

代码语言:javascript复制
df.sample(n=3,weights=df['Math'].values).head()

0 人点赞