第二章 索引-学习笔记

第二章 索引

import numpy as np
import pandas as pd

一、索引器

1. 表的列索引

列索引是最常见的索引形式,一般通过[]来实现。通过[列名]可以从DataFrame中取出相应的列,返回值为Series,例如从表中取出姓名一列:

# 读取数据,usecols 取所需列
df = pd.read_csv('data/learn_pandas.csv', usecols = ['School', 'Grade', 'Name', 'Gender', 'Weight', 'Transfer'])
df['Name'].head()
0      Gaopeng Yang
1    Changqiang You
2           Mei Sun
3      Xiaojuan Sun
4       Gaojuan You
Name: Name, dtype: object

如果列名不包含空格,可以直接.列名取,等价于[列名],仅针对于取出单列:

df.Name.head()
0      Gaopeng Yang
1    Changqiang You
2           Mei Sun
3      Xiaojuan Sun
4       Gaojuan You
Name: Name, dtype: object

取多列,就用[列名组成的列表],中间用逗号隔开,返回的是DataFrame,例如从表中取出性别和姓名两列:

df[['Gender', 'Name']].head()
GenderName
0FemaleGaopeng Yang
1MaleChangqiang You
2MaleMei Sun
3FemaleXiaojuan Sun
4MaleGaojuan You

2. 序列的行索引

【a】以字符串为索引的Series

如果取出单个索引的对应元素,则可以使用[item]
若Series只有单个值对应,则返回这个标量值
如果有多个值对应,则返回一个Series:

s = pd.Series([1, 2, 3, 4, 5, 6], index=['a', 'b', 'a', 'a', 'a', 'c'])
s['a']
a    1
a    3
a    4
a    5
dtype: int64
s['b']
2

如果取出多个索引的对应元素,则可以使用[items的列表]:

s[['c','b']]
c    6
b    2
dtype: int64

如果想要取出某两个索引之间的元素,并且这两个索引是在整个索引中唯一出现,则可以使用切片,同时需要注意这里的切片会包含两个端点:

s['b':'c':2] # 元素索引b→c,步长为2
b    2
a    4
c    6
dtype: int64

【b】以整数为索引的Series

数据读入,如果不特别指定所对应的列作为索引,会生成从0开始的整数索引作为默认索引。

和字符串一样,如果使用[int]或[int_list],则可以取出对应索引元素的值:

s = pd.Series(['a', 'b', 'c', 'd', 'e', 'f'], index=[1, 3, 1, 2, 5, 4])
s[1]
1    a
1    c
dtype: object
s[[2,3]]
2    d
3    b
dtype: object

如果使用整数切片,则会取出对应索引位置的值
这里的整数切片同Python中的切片一样是开区间,不包含右端点:

s[1:-1:1] # 位置索引1→末尾,步长为1,不包右端点
3    b
1    c
2    d
5    e
dtype: object

【WARNING】关于索引类型的说明

不要把纯浮点以及任何混合类型(字符串、整数、浮点类型等的混合)作为索引,操作时报错或者返回非预期的结果

3. loc索引器

备注:.loc 可以把对应的数据信息全都显示出来
对于表而言,有两种索引器:
①基于元素的loc索引器:loc[*, *],第一个*代表行的选择,第二个*代表列的选择,如果直接locloc[*],这个*是指行的筛选
*的位置一共有五类合法对象,分别是:单个元素、元素列表、元素切片、布尔列表以及函数
②基于位置的iloc索引器

为了演示相应操作,先利用set_index方法把Name列设为索引

df_demo = df.set_index('Name')
df_demo.head()
SchoolGradeGenderWeightTransfer
Name
Gaopeng YangShanghai Jiao Tong UniversityFreshmanFemale46.0N
Changqiang YouPeking UniversityFreshmanMale70.0N
Mei SunShanghai Jiao Tong UniversitySeniorMale89.0N
Xiaojuan SunFudan UniversitySophomoreFemale41.0N
Gaojuan YouFudan UniversitySophomoreMale74.0N

【a】*为单个元素

直接取出相应的行或列,如果该元素在索引中重复则结果为DataFrame,否则为Series:

df_demo.loc['Qiang Sun'] # 多个人叫此名字,重复则结果为DataFrame
SchoolGradeGenderWeightTransfer
Name
Qiang SunTsinghua UniversityJuniorFemale53.0N
Qiang SunTsinghua UniversitySophomoreFemale40.0N
Qiang SunShanghai Jiao Tong UniversityJuniorFemaleNaNN
df_demo.loc['Quan Zhao'] # 名字唯一,为Series
School      Shanghai Jiao Tong University
Grade                              Junior
Gender                             Female
Weight                                 53
Transfer                                N
Name: Quan Zhao, dtype: object

也可以同时选择行和列:

df_demo.loc['Qiang Sun','School']
# 姓名为索引,又重复,返回Series
Name
Qiang Sun              Tsinghua University
Qiang Sun              Tsinghua University
Qiang Sun    Shanghai Jiao Tong University
Name: School, dtype: object
df_demo.loc['Quan Zhao','School']
# 返回单个元素
'Shanghai Jiao Tong University'

【b】*为元素列表

取出列表中所有元素值对应的行或列:

df_demo.loc[['Qiang Sun','Quan Zhao'],['School','Gender']]
SchoolGender
Name
Qiang SunTsinghua UniversityFemale
Qiang SunTsinghua UniversityFemale
Qiang SunShanghai Jiao Tong UniversityFemale
Quan ZhaoShanghai Jiao Tong UniversityFemale

【c】*为切片

之前的Series使用字符串索引时提到,如果是唯一值的起点和终点字符,那么就可以使用切片,并且包含两个端点,如果不唯一则报错:

df_demo.loc['Gaojuan You':'Gaoqiang Qian','School':'Gender']
SchoolGradeGender
Name
Gaojuan YouFudan UniversitySophomoreMale
Xiaoli QianTsinghua UniversityFreshmanFemale
Qiang ChuShanghai Jiao Tong UniversityFreshmanFemale
Gaoqiang QianTsinghua UniversityJuniorFemale

需要注意的是,如果DataFrame使用整数索引,其使用整数切片的时候和上面字符串索引的要求一致,都是元素切片,包含端点且起点、终点不允许有重复值

df_loc_slice_demo = df_demo.copy()
df_loc_slice_demo.index = range(df_demo.shape[0],0,-1)
# range(start, stop,[step]),df_demo的行数,到0,逐步减1
df_loc_slice_demo.loc[5:3]
SchoolGradeGenderWeightTransfer
5Fudan UniversityJuniorFemale46.0N
4Tsinghua UniversitySeniorFemale50.0N
3Shanghai Jiao Tong UniversitySeniorFemale45.0N
# 没有返回,说明不是整数位置切片
df_loc_slice_demo.loc[3:5]
SchoolGradeGenderWeightTransfer

【d】*为布尔列表

在实际的数据处理中,根据条件来筛选行是极其常见的,此处传入loc的布尔列表与DataFrame长度相同,且列表为True的位置所对应的行会被选中,False则会被剔除。

# 例如,选出体重超过70kg的学生:
df_demo.loc[df_demo.Weight>70].head()
SchoolGradeGenderWeightTransfer
Name
Mei SunShanghai Jiao Tong UniversitySeniorMale89.0N
Gaojuan YouFudan UniversitySophomoreMale74.0N
Xiaopeng ZhouShanghai Jiao Tong UniversityFreshmanMale74.0N
Xiaofeng SunTsinghua UniversitySeniorMale71.0N
Qiang ZhengShanghai Jiao Tong UniversitySeniorMale87.0N

前面所提到的传入元素列表,也可以通过isin方法返回的布尔列表等价写出

# 例如,选出所有大一和大四的同学信息:
df_demo.loc[df_demo['Grade'].isin(['Freshman','Senior'])].head()
# 此处df_demo['Grade']在名字没有空格的情况下可以直接df_demo.Grade
SchoolGradeGenderWeightTransfer
Name
Gaopeng YangShanghai Jiao Tong UniversityFreshmanFemale46.0N
Changqiang YouPeking UniversityFreshmanMale70.0N
Mei SunShanghai Jiao Tong UniversitySeniorMale89.0N
Xiaoli QianTsinghua UniversityFreshmanFemale51.0N
Qiang ChuShanghai Jiao Tong UniversityFreshmanFemale52.0N

对于复合条件而言,可以用|(或), &(且), ~(取反)的组合来实现

# 例如,选出复旦大学中体重超过70kg的大四学生,或者北大男生中体重超过80kg的非大四的学生:
condition1_1 = df_demo['School'] == 'Fudan University'
condition1_2 = df_demo['Grade'] =='Senior'
condition1_3 = df_demo['Weight'] >70
condition1 = condition1_1&condition1_2&condition1_3
condition2_1 = df_demo['School'] == 'Peking University'
condition2_2 = df_demo['Grade'] =='Senior'
condition2_3 = df_demo['Weight'] >80
condition2 = condition2_1&(~condition2_2)&condition2_3
df_demo.loc[condition1 | condition2]
# 打印出详细的信息,用.loc[]
SchoolGradeGenderWeightTransfer
Name
Qiang HanPeking UniversityFreshmanMale87.0N
Chengpeng ZhouFudan UniversitySeniorMale81.0N
Changpeng ZhaoPeking UniversityFreshmanMale83.0N
Chengpeng QianFudan UniversitySeniorMale73.0Y

【练一练】

select_dtypes是一个实用函数,它能够从表中选出相应类型的列,若要选出所有数值型的列,只需使用.select_dtypes(‘number’),请利用布尔列表选择的方法结合DataFrame的dtypes属性在learn_pandas数据集上实现这个功能。

# 首先先查看数据类型
df_demo.dtypes
School       object
Grade        object
Gender       object
Weight      float64
Transfer     object
dtype: object
# 用select_dtype选择
df_demo.select_dtypes('float64').head()
Weight
Name
Gaopeng Yang46.0
Changqiang You70.0
Mei Sun89.0
Xiaojuan Sun41.0
Gaojuan You74.0
# 用布尔类型筛选
df_demo.loc[df_demo.Weight.dtype =='float64']
# 这个会报错,但是直接df_demo.Weight.dtype =='float64'会返回true

.select_dtypesh知识点扩充

df_demo.select_dtypes(include=['float64']).head() # 筛选出loat64的列
Weight
Name
Gaopeng Yang46.0
Changqiang You70.0
Mei Sun89.0
Xiaojuan Sun41.0
Gaojuan You74.0
df_demo.select_dtypes(exclude=['object']).head()
# 不包含object的列
Weight
Name
Gaopeng Yang46.0
Changqiang You70.0
Mei Sun89.0
Xiaojuan Sun41.0
Gaojuan You74.0
df_demo.select_dtypes(include=['float64'], exclude=None).head()
# include包含需要获取的列类型,exclude包含不需要获取的数据类型

【e】*为函数

这里的函数,必须以前面的四种合法形式之一为返回值,并且函数的输入值为DataFrame本身。假设仍然是上述复合条件筛选的例子,可以把逻辑写入一个函数中再返回,需要注意的是函数的形式参数x本质上即为df_demo:

def condition(x):
    condition_1_1 = x.School == 'Fudan University'
    condition_1_2 = x.Grade == 'Senior'
    condition_1_3 = x.Weight > 70
    condition_1 = condition_1_1 & condition_1_2 & condition_1_3
    condition_2_1 = x.School == 'Peking University'
    condition_2_2 = x.Grade == 'Senior'
    condition_2_3 = x.Weight > 80
    condition_2 = condition_2_1 & (~condition_2_2) & condition_2_3
    result = condition_1 | condition_2
    return result
df_demo.loc[condition]
SchoolGradeGenderWeightTransfer
Name
Qiang HanPeking UniversityFreshmanMale87.0N
Chengpeng ZhouFudan UniversitySeniorMale81.0N
Changpeng ZhaoPeking UniversityFreshmanMale83.0N
Chengpeng QianFudan UniversitySeniorMale73.0Y

此外,还支持使用lambda表达式,其返回值也同样必须是先前提到的四种形式之一:

df_demo.loc[lambda x:'Quan Zhao',lambda x:'Gender']
'Female'

由于函数无法返回如start: end: step的切片形式,故返回切片时要用slice对象进行包装:

df_demo.loc[lambda x:slice('Gaojuan You','Gaoqiang Qian')]
SchoolGradeGenderWeightTransfer
Name
Gaojuan YouFudan UniversitySophomoreMale74.0N
Xiaoli QianTsinghua UniversityFreshmanFemale51.0N
Qiang ChuShanghai Jiao Tong UniversityFreshmanFemale52.0N
Gaoqiang QianTsinghua UniversityJuniorFemale50.0N

Series也可以使用loc索引,其遵循的原则与DataFrame中用于行筛选的loc[*]完全一致.

【WARNING】不要使用链式赋值

在对表或者序列赋值时,应当在使用一层索引器后直接进行赋值操作,这样做是由于进行多次索引后赋值是赋在临时返回的copy副本上的,而没有真正修改元素从而报出SettingWithCopyWarning警告。例如,下面给出的例子:

df_chain = pd.DataFrame([[0,0],[1,0],[-1,0]],columns=list('AB'))
df_chain
AB
000
110
2-10
import warnings
with warnings.catch_warnings():
    warnings.filterwarnings('error')
    try:
        df_chain[df_chain.A!=0].B = 1 
        # 使用方括号列索引后,再使用点的列索引
    except Warning as w:
         Warning_Msg = w
print(Warning_Msg)
df_chain
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
AB
000
110
2-10
df_chain.loc[df_chain.A!=0,'B'] = 1
df_chain
AB
000
111
2-11

4. iloc索引器

iloc的使用与loc完全类似,只不过是针对位置进行筛选
在相应的*位置处一共也有五类合法对象,分别是:
整数、整数列表、整数切片、布尔列表以及函数
函数的返回值必须是前面的四类合法对象中的一个,其输入同样也为DataFrame本身。

# 第二行第二列
df_demo.iloc[1,1]
'Freshman'
# 前两行前两列
df_demo.iloc[[0,1],[0,1]]
SchoolGrade
Name
Gaopeng YangShanghai Jiao Tong UniversityFreshman
Changqiang YouPeking UniversityFreshman
# 切片不包含结束端点-右端点
df_demo.iloc[1:4,2:4]
# 传入切片为返回值的函数,函数无法返回如start: end: step的切片形式,故返回切片时要用slice对象进行包装
df_demo.iloc[lambda x:slice(1,4)]
SchoolGradeGenderWeightTransfer
Name
Changqiang YouPeking UniversityFreshmanMale70.0N
Mei SunShanghai Jiao Tong UniversitySeniorMale89.0N
Xiaojuan SunFudan UniversitySophomoreFemale41.0N

使用布尔列表,不能传入Series而必须传入序列的values,否则会报错。
因此,在使用布尔筛选的时候还是应当优先考虑loc的方式。

# 例如,选出体重超过80kg的学生:
df_demo.iloc[(df_demo['Weight']>80).values].head()
SchoolGradeGenderWeightTransfer
Name
Mei SunShanghai Jiao Tong UniversitySeniorMale89.0N
Qiang ZhengShanghai Jiao Tong UniversitySeniorMale87.0N
Qiang HanPeking UniversityFreshmanMale87.0N
Chengpeng ZhouFudan UniversitySeniorMale81.0N
Feng HanShanghai Jiao Tong UniversitySophomoreMale82.0N

对Series而言同样也可以通过iloc返回相应位置的值或子序列:

df_demo['School'].iloc[1]
'Peking University'
df_demo['School'].iloc[1:5:2]
# 从位置1开始到5,步长为2,不包含右端点
Name
Changqiang You    Peking University
Xiaojuan Sun       Fudan University
Name: School, dtype: object

5. query方法–减少代码行数

在pandas中,支持把字符串形式的查询表达式传入query方法来查询数据,其表达式的执行结果必须返回布尔列表。
在进行复杂索引时,由于这种检索方式无需像普通方法一样重复使用DataFrame的名字来引用列名,一般而言会使代码长度在不降低可读性的前提下有所减少。

# 例如,将loc一节中的复合条件查询例子可以如下改写:
df.query('((School == "Fudan University")&'
         '(Grade == "Senior")&'
         '(Weight > 70))|'
         '((School == "Peking University")&'
         '(Grade != "Senior")&'
         '(Weight > 80))')
SchoolGradeNameGenderWeightTransfer
38Peking UniversityFreshmanQiang HanMale87.0N
66Fudan UniversitySeniorChengpeng ZhouMale81.0N
99Peking UniversityFreshmanChangpeng ZhaoMale83.0N
131Fudan UniversitySeniorChengpeng QianMale73.0Y

在query表达式中,帮用户注册了所有来自DataFrame的列名,所有属于该Series的方法都可以被调用,和正常的函数调用并没有区别

# 例如,查询体重超过均值的学生:
df.query('Weight>Weight.mean()').head()
SchoolGradeNameGenderWeightTransfer
1Peking UniversityFreshmanChangqiang YouMale70.0N
2Shanghai Jiao Tong UniversitySeniorMei SunMale89.0N
4Fudan UniversitySophomoreGaojuan YouMale74.0N
10Shanghai Jiao Tong UniversityFreshmanXiaopeng ZhouMale74.0N
14Tsinghua UniversitySeniorXiaomei ZhouFemale57.0N

【NOTE】query中引用带空格的列名

对于含有空格的列名,需要使用`col name`的方式进行引用

同时,在query中还注册了若干英语的字面用法,帮助提高可读性。
例如:or, and, or, is in, not in。

# 例如,筛选出男生中不是大一大二的学生:
df.query('(Grade not in ["Freshman","Sophomore"]) and (Gender == "Male")').head()
SchoolGradeNameGenderWeightTransfer
2Shanghai Jiao Tong UniversitySeniorMei SunMale89.0N
16Tsinghua UniversityJuniorXiaoqiang QinMale68.0N
17Tsinghua UniversityJuniorPeng WangMale65.0N
18Tsinghua UniversitySeniorXiaofeng SunMale71.0N
21Shanghai Jiao Tong UniversitySeniorXiaopeng ShenMale62.0NaN

此外,在字符串中出现与列表的比较时,==和!=分别表示元素出现在列表和没有出现在列表。
等价于is in和not in

# 例如,查询所有大三和大四的学生:
df.query('Grade ==["Junior","Senior"]').head()
SchoolGradeNameGenderWeightTransfer
2Shanghai Jiao Tong UniversitySeniorMei SunMale89.0N
7Tsinghua UniversityJuniorGaoqiang QianFemale50.0N
9Peking UniversityJuniorJuan XuFemaleNaNN
11Tsinghua UniversityJuniorXiaoquan LvFemale43.0N
12Shanghai Jiao Tong UniversitySeniorPeng YouFemale48.0NaN

对于query中的字符串,如果要引用外部变量,只需在变量名前加@符号。

# 例如,取出体重位于70kg到80kg之间的学生:
low,high= 70,80
df.query('(Weight>=@low)&''(Weight<=@high)').head()
# df.query('Weight.between(@low, @high)').head()
#  👇
# 报错:'Series' objects are mutable, thus they cannot be hashed
SchoolGradeNameGenderWeightTransfer
1Peking UniversityFreshmanChangqiang YouMale70.0N
4Fudan UniversitySophomoreGaojuan YouMale74.0N
10Shanghai Jiao Tong UniversityFreshmanXiaopeng ZhouMale74.0N
18Tsinghua UniversitySeniorXiaofeng SunMale71.0N
35Peking UniversityFreshmanGaoli ZhaoMale78.0N
# 例如,查询所有大三和大四的学生:
level = ["Junior","Senior"]
df.query('Grade == @level').head()
SchoolGradeNameGenderWeightTransfer
2Shanghai Jiao Tong UniversitySeniorMei SunMale89.0N
7Tsinghua UniversityJuniorGaoqiang QianFemale50.0N
9Peking UniversityJuniorJuan XuFemaleNaNN
11Tsinghua UniversityJuniorXiaoquan LvFemale43.0N
12Shanghai Jiao Tong UniversitySeniorPeng YouFemale48.0NaN

6. 随机抽样

如果把DataFrame的每一行看作一个样本,或把每一列看作一个特征,再把整个DataFrame看作总体,想要对样本或特征进行随机抽样就可以用sample函数。
有时在拿到大型数据集后,想要对统计特征进行计算来了解数据的大致分布,但是这很费时间。
同时,由于许多统计特征在等概率不放回的简单随机抽样条件下,是总体统计特征的无偏估计,比如样本均值和总体均值,那么就可以先从整张表中抽出一部分来做近似估计。

sample函数中的主要参数:
n→抽样数量
axis→抽样的方向(0为行、1为列)
frac→抽样比例(0.3则为从总体中抽出30%的样本)
replace→是否放回,当replace = True则表示有放回抽样
weights→每个样本的抽样相对概率

df_sample = pd.DataFrame({'id': list('abcde'), 'value': [1, 2, 3, 4, 90]})
df_sample
idvalue
0a1
1b2
2c3
3d4
4e90
# 例如,对df_sample以value值的相对大小为抽样概率进行有放回抽样,抽样数量为3。
df_sample.sample(3,replace=True,weights=df_sample.value)
idvalue
4e90
4e90
4e90

二、多级索引

1. 多级索引及其表的结构

为了更加清晰地说明具有多级索引的DataFrame结构,下面新构造一张表

np.random.seed(0)
multi_index = pd.MultiIndex.from_product([list('ABCD'), df.Gender.unique()], names=('School', 'Gender'))
multi_column = pd.MultiIndex.from_product([['Height', 'Weight'], df.Grade.unique()], names=('Indicator', 'Grade'))
df_multi = pd.DataFrame(np.c_[(np.random.randn(8,4)*5 + 163).tolist(), (np.random.randn(8,4)*5 + 65).tolist()],
                        index = multi_index, columns = multi_column).round(1)
df_multi
IndicatorHeightWeight
GradeFreshmanSeniorSophomoreJuniorFreshmanSeniorSophomoreJunior
SchoolGender
AFemale171.8165.0167.9174.260.655.163.365.8
Male172.3158.1167.8162.271.271.063.163.5
BFemale162.5165.1163.7170.359.857.956.574.8
Male166.8163.6165.2164.762.562.858.768.9
CFemale170.5162.0164.6158.756.963.960.566.9
Male150.2166.3167.3159.362.459.164.967.1
DFemale174.3155.7163.2162.165.366.561.863.2
Male170.7170.3163.8164.961.663.260.956.4

下图通过颜色区分,标记了DataFrame的结构。与单层索引的表一样,具备元素值、行索引和列索引三个部分。其中,这里的行索引和列索引都是MultiIndex类型,只不过索引中的一个元素是元组而不是单层索引中的标量。例如,行索引的第四个元素为(“B”, “Male”),列索引的第二个元素为(“Height”, “Senior”),这里需要注意,外层连续出现相同的值时,第一次之后出现的会被隐藏显示,使结果的可读性增强。

与单层索引类似,MultiIndex也具有名字属性,图中的School和Gender分别对应了表的第一层和第二层行索引的名字,Indicator和Grade分别对应了第一层和第二层列索引的名字。

索引的名字和值属性分别可以通过names和values获得:

df_multi.index.names # 获取行索引名
FrozenList(['School', 'Gender'])
df_multi.index.values#获取行索引的值
array([('A', 'Female'), ('A', 'Male'), ('B', 'Female'), ('B', 'Male'),
       ('C', 'Female'), ('C', 'Male'), ('D', 'Female'), ('D', 'Male')],
      dtype=object)
df_multi.columns.names#获取列索引名
FrozenList(['Indicator', 'Grade'])
df_multi.columns.values#获取列索引的值
array([('Height', 'Freshman'), ('Height', 'Senior'),
       ('Height', 'Sophomore'), ('Height', 'Junior'),
       ('Weight', 'Freshman'), ('Weight', 'Senior'),
       ('Weight', 'Sophomore'), ('Weight', 'Junior')], dtype=object)

如果想要得到某一层的索引,则需要通过get_level_values获得:

df_multi.index.get_level_values(0)
#第一层行索引的值
Index(['A', 'A', 'B', 'B', 'C', 'C', 'D', 'D'], dtype='object', name='School')

但对于索引而言,无论是单层还是多层,用户都无法通过index_obj[0] = item的方式来修改元素,也不能通过index_name[0] = new_name的方式来修改名字,关于如何修改这些属性的话题将在第三节被讨论。

2. 多级索引中的loc索引器

熟悉了结构后,现在回到原表,将学校和年级设为索引,此时的行为多级索引,列为单级索引,由于默认状态的列索引不含名字,因此对应于刚刚图中Indicator和Grade的索引名位置是空缺的。

df_multi = df.set_index(['School','Grade'])
df_multi.head()
NameGenderWeightTransfer
SchoolGrade
Shanghai Jiao Tong UniversityFreshmanGaopeng YangFemale46.0N
Peking UniversityFreshmanChangqiang YouMale70.0N
Shanghai Jiao Tong UniversitySeniorMei SunMale89.0N
Fudan UniversitySophomoreXiaojuan SunFemale41.0N
SophomoreGaojuan YouMale74.0N

由于多级索引中的单个元素以元组为单位,因此在之前介绍的loc和iloc方法完全可以照搬,只需把标量的位置替换成对应的元组,不过在索引前最好对MultiIndex进行排序以避免性能警告:

df_multi = df_multi.sort_index() 
# 对MultiIndex进行排序以避免性能警告
df_multi.loc[('Fudan University', 'Junior')].head()
NameGenderWeightTransfer
SchoolGrade
Fudan UniversityJuniorYanli YouFemale48.0N
JuniorChunqiang ChuMale72.0N
JuniorChangfeng LvMale76.0N
JuniorYanjuan LvFemale49.0NaN
JuniorGaoqiang ZhouFemale43.0N
df_multi.loc[[('Fudan University', 'Senior'),('Shanghai Jiao Tong University', 'Freshman')]].head()
NameGenderWeightTransfer
SchoolGrade
Fudan UniversitySeniorChengpeng ZhengFemale38.0N
SeniorFeng ZhouFemale47.0N
SeniorGaomei LvFemale34.0N
SeniorChunli LvFemale56.0N
SeniorChengpeng ZhouMale81.0N
# 布尔列表也是可用的
df_multi.loc[df_multi.Weight>70].head()
NameGenderWeightTransfer
SchoolGrade
Fudan UniversityFreshmanFeng WangMale74.0N
JuniorChunqiang ChuMale72.0N
JuniorChangfeng LvMale76.0N
SeniorChengpeng ZhouMale81.0N
SeniorChengpeng QianMale73.0Y
# 函数
df_multi.loc[lambda x:('Fudan University','Junior')].head()
NameGenderWeightTransfer
SchoolGrade
Fudan UniversityJuniorYanli YouFemale48.0N
JuniorChunqiang ChuMale72.0N
JuniorChangfeng LvMale76.0N
JuniorYanjuan LvFemale49.0NaN
JuniorGaoqiang ZhouFemale43.0N
【练一练】

与单层索引类似,若存在重复元素,则不能使用切片,请去除重复索引后给出一个元素切片的例子。

# 去除重复索引
df_multi_slice_demo = df_multi[~df_multi.index.duplicated()] 
df_multi_slice_demo
NameGenderWeightTransfer
SchoolGrade
Fudan UniversityFreshmanChangqiang YangFemale49.0N
JuniorYanli YouFemale48.0N
SeniorChengpeng ZhengFemale38.0N
SophomoreXiaojuan SunFemale41.0N
Peking UniversityFreshmanChangqiang YouMale70.0N
JuniorJuan XuFemaleNaNN
SeniorChangli LvFemale41.0N
SophomoreChangmei XuFemale43.0N
Shanghai Jiao Tong UniversityFreshmanGaopeng YangFemale46.0N
JuniorFeng ZhengFemale51.0N
SeniorMei SunMale89.0N
SophomoreYanfeng QianFemale48.0N
Tsinghua UniversityFreshmanXiaoli QianFemale51.0N
JuniorGaoqiang QianFemale50.0N
SeniorXiaomei ZhouFemale57.0N
SophomoreLi WangMale79.0N
# 唯一值的起点和终点字使用切片,含两个端点,不唯一则报错
df_multi_slice_demo.loc['Fudan University':'Shanghai Jiao Tong University']
NameGenderWeightTransfer
SchoolGrade
Fudan UniversityFreshmanChangqiang YangFemale49.0N
JuniorYanli YouFemale48.0N
SeniorChengpeng ZhengFemale38.0N
SophomoreXiaojuan SunFemale41.0N
Peking UniversityFreshmanChangqiang YouMale70.0N
JuniorJuan XuFemaleNaNN
SeniorChangli LvFemale41.0N
SophomoreChangmei XuFemale43.0N
Shanghai Jiao Tong UniversityFreshmanGaopeng YangFemale46.0N
JuniorFeng ZhengFemale51.0N
SeniorMei SunMale89.0N
SophomoreYanfeng QianFemale48.0N
# 或者列也筛选一下
df_multi_slice_demo.loc['Fudan University':'Shanghai Jiao Tong University','Name':'Gender']
NameGender
SchoolGrade
Fudan UniversityFreshmanChangqiang YangFemale
JuniorYanli YouFemale
SeniorChengpeng ZhengFemale
SophomoreXiaojuan SunFemale
Peking UniversityFreshmanChangqiang YouMale
JuniorJuan XuFemale
SeniorChangli LvFemale
SophomoreChangmei XuFemale
Shanghai Jiao Tong UniversityFreshmanGaopeng YangFemale
JuniorFeng ZhengFemale
SeniorMei SunMale
SophomoreYanfeng QianFemale

此外,在多级索引中的元组有一种特殊的用法,可以对多层的元素进行交叉组合后索引,但同时需要指定loc的列,全选则用:表示
其中,每一层需要选中的元素用列表存放,传入loc的形式为[(level_0_list, level_1_list), cols]

# 例如,想要得到所有北大和复旦的大二大三学生,可以如下写出:
res=df_multi.loc[(['Peking University', 'Fudan University'],['Sophomore', 'Junior']),:]
res.head()
# 全选则用:表示,指定loc的列如下:
# res=df_multi.loc[(['Peking University', 'Fudan University'],['Sophomore', 'Junior']),('Name','Weight')]
NameGenderWeightTransfer
SchoolGrade
Peking UniversitySophomoreChangmei XuFemale43.0N
SophomoreXiaopeng QinMaleNaNN
SophomoreMei XuFemale39.0N
SophomoreXiaoli ZhouFemale55.0N
SophomorePeng HanFemale34.0NaN
res.shape
(33, 4)

下面的语句和上面类似,前者是小括号包括中括号,后者是中括号包裹小括号,但仍然传入的是元素(这里为元组)的列表,它们的意义是不同的

# 选出北大的大三学生和复旦的大二学生:
res=df_multi.loc[[('Peking University', 'Junior'), ('Fudan University', 'Sophomore')]]
res.head()
NameGenderWeightTransfer
SchoolGrade
Peking UniversityJuniorJuan XuFemaleNaNN
JuniorChangjuan YouFemale47.0N
JuniorGaoli XuFemale48.0N
JuniorGaoquan ZhouMale70.0N
JuniorQiang YouFemale56.0N
res.shape
(16, 4)

3. IndexSlice对象

前面介绍的方法,即使在索引不重复的时候,也只能对元组整体进行切片,而不能对每层进行切片,也不允许将切片和布尔列表混合使用,引入IndexSlice对象就能解决这个问题。
Slice对象一共有两种形式:
第一种为loc[idx[*,*]]
第二种为loc[idx[*,*],idx[*,*]]型,下面将进行介绍。
为了方便演示,下面构造一个索引不重复的`的DataFrame:

np.random.seed(0)
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_ex = pd.DataFrame(np.random.randint(-9,10,(9,9)), index=mul_index1, columns=mul_index2)
df_ex
BigDEF
Smalldefdefdef
UpperLower
Aa36-9-6-6-209-5
b-33-8-3-258-44
c-107-466-99-6
Ba85-2-9-80-91-6
b29-7-9-9-5-4-3-1
c86-501-8-8-20
Ca-6-3259-95-63
b12-5-3-56-63-5
c-156-66478-4

为了使用silce对象,先要进行定义:

idx = pd.IndexSlice

【a】loc[idx[*,*]]

这种情况并不能进行多层分别切片,前一个*表示行的选择,后一个*表示列的选择,与单纯的loc是类似的:

# 选取行索引为C,类索引为D中的f及其后面所有
df_ex.loc[idx['C':,('D','f'):]]
BigDEF
Smallfdefdef
UpperLower
Ca259-95-63
b-5-3-56-63-5
c6-66478-4

另外,也支持布尔序列的索引:

# 列和大于0的数,再取对应的行索引到A
df_ex.loc[idx[:'A',lambda x:x.sum()>0]]
BigDF
Smalldee
UpperLower
Aa369
b-33-4
c-109

【b】loc[idx[*,*],idx[*,*]]

这种情况能够分层进行切片,前一个idx指代的是行索引,后一个是列索引。

df_ex.loc[idx[:'A','b':],idx['E':,'e':]]
BigEF
Smallefef
UpperLower
Ab-25-44
c669-6

4. 多级索引的构造

前面提到了多级索引表的结构和切片,那么除了使用set_index之外,如何自己构造多级索引呢?
常用的三种方法:
from_tuples, from_arrays, from_product
它们都是pd.MultiIndex对象下的函数。

my_tuple = [('a','cat'),('a','dog'),('b','cat'),('b','dog')]
my_tuple
[('a', 'cat'), ('a', 'dog'), ('b', 'cat'), ('b', 'dog')]

from_tuples指根据传入由元组组成的列表进行构造:

pd.MultiIndex.from_tuples(my_tuple, names=['First','Second'])
MultiIndex([('a', 'cat'),
            ('a', 'dog'),
            ('b', 'cat'),
            ('b', 'dog')],
           names=['First', 'Second'])

from_arrays指根据传入列表中,对应层的列表进行构造:

my_array = [list('aabb'), ['cat', 'dog']*2]
pd.MultiIndex.from_arrays(my_array, names=['First','Second'])
MultiIndex([('a', 'cat'),
            ('a', 'dog'),
            ('b', 'cat'),
            ('b', 'dog')],
           names=['First', 'Second'])

from_product指根据给定多个列表的笛卡尔积进行构造:

my_list1 = ['a','b']
my_list2 = ['cat','dog']
pd.MultiIndex.from_product([my_list1, my_list2], names=['First','Second'])
MultiIndex([('a', 'cat'),
            ('a', 'dog'),
            ('b', 'cat'),
            ('b', 'dog')],
           names=['First', 'Second'])

三、索引的常用方法

1. 索引层的交换和删除

为了方便理解交换的过程,这里构造一个三级索引的例子:

np.random.seed(0)
L1,L2,L3 = ['A','B'],['a','b'],['alpha','beta']
mul_index1 = pd.MultiIndex.from_product([L1,L2,L3], names=('Upper', 'Lower','Extra'))
L4,L5,L6 = ['C','D'],['c','d'],['cat','dog']
mul_index2 = pd.MultiIndex.from_product([L4,L5,L6], names=('Big', 'Small', 'Other'))
df_ex = pd.DataFrame(np.random.randint(-9,10,(8,8)), index=mul_index1,  columns=mul_index2)
df_ex
BigCD
Smallcdcd
Othercatdogcatdogcatdogcatdog
UpperLowerExtra
Aaalpha36-9-6-6-209
beta-5-33-8-3-258
balpha-44-107-466
beta-99-685-2-9-8
Baalpha0-91-629-7-9
beta-9-5-4-3-186-5
balpha01-8-8-20-6-3
beta259-95-631

索引层的交换由swaplevel和reorder_levels完成
swaplevel→前者只能交换两个层
reorder_levels→可以交换任意层
两者都可以指定交换的是轴是哪一个,即行索引或列索引:

# 列索引的第一层和第三层交换
df_ex.swaplevel(0,2,axis=1).head() 
Othercatdogcatdogcatdogcatdog
Smallccddccdd
BigCCCCDDDD
UpperLowerExtra
Aaalpha36-9-6-6-209
beta-5-33-8-3-258
balpha-44-107-466
beta-99-685-2-9-8
Baalpha0-91-629-7-9
# 列表数字指代原来索引中的层
# 原来第三层索引现在到第一层,第一层索引到现在第二层位置,第二层索引到现在第三层位置
df_ex.reorder_levels([2,0,1],axis=0).head()
BigCD
Smallcdcd
Othercatdogcatdogcatdogcatdog
ExtraUpperLower
alphaAa36-9-6-6-209
betaAa-5-33-8-3-258
alphaAb-44-107-466
betaAb-99-685-2-9-8
alphaBa0-91-629-7-9
【NOTE】轴之间的索引交换

这里只涉及行或列索引内部的交换,不同方向索引之间的交换将在第五章中被讨论。

若想要删除某一层的索引,可以使用droplevel方法:

# 删除列索引第二层 small
df_ex.droplevel(1,axis=1)
BigCD
Othercatdogcatdogcatdogcatdog
UpperLowerExtra
Aaalpha36-9-6-6-209
beta-5-33-8-3-258
balpha-44-107-466
beta-99-685-2-9-8
Baalpha0-91-629-7-9
beta-9-5-4-3-186-5
balpha01-8-8-20-6-3
beta259-95-631
# 删除行索引第一、第二层
df_ex.droplevel([0,1],axis=0)
BigCD
Smallcdcd
Othercatdogcatdogcatdogcatdog
Extra
alpha36-9-6-6-209
beta-5-33-8-3-258
alpha-44-107-466
beta-99-685-2-9-8
alpha0-91-629-7-9
beta-9-5-4-3-186-5
alpha01-8-8-20-6-3
beta259-95-631

2. 索引属性的修改

通过rename_axis可以对索引层的名字进行修改,常用的修改方式是传入字典的映射:

df_ex.rename_axis(index={'Upper':'Changed_row'},columns={'Other':'Changed_col'}).head()
BigCD
Smallcdcd
Changed_colcatdogcatdogcatdogcatdog
Changed_rowLowerExtra
Aaalpha36-9-6-6-209
beta-5-33-8-3-258
balpha-44-107-466
beta-99-685-2-9-8
Baalpha0-91-629-7-9

通过rename可以对索引的值进行修改,如果是多级索引需要指定修改的层号level:

# 将列上的第三层(Other)的cat列名更改为not_cat
df_ex.rename(columns={'cat':'not_cat'},level=2).head()
BigCD
Smallcdcd
Othernot_catdognot_catdognot_catdognot_catdog
UpperLowerExtra
Aaalpha36-9-6-6-209
beta-5-33-8-3-258
balpha-44-107-466
beta-99-685-2-9-8
Baalpha0-91-629-7-9

传入参数也可以是函数,其输入值就是索引元素

# 将行上的第三层(Extra	)的名字都改成大写
df_ex.rename(index=lambda x:str.upper(x),level=2).head()
BigCD
Smallcdcd
Othercatdogcatdogcatdogcatdog
UpperLowerExtra
AaALPHA36-9-6-6-209
BETA-5-33-8-3-258
bALPHA-44-107-466
BETA-99-685-2-9-8
BaALPHA0-91-629-7-9
【练一练】

尝试在rename_axis中使用函数完成与例子中一样的功能。

df_ex.rename_axis(index=lambda x:str.upper(x),columns=lambda x:str.upper(x))
BIGCD
SMALLcdcd
OTHERcatdogcatdogcatdogcatdog
UPPERLOWEREXTRA
Aaalpha36-9-6-6-209
beta-5-33-8-3-258
balpha-44-107-466
beta-99-685-2-9-8
Baalpha0-91-629-7-9
beta-9-5-4-3-186-5
balpha01-8-8-20-6-3
beta259-95-631

对于整个索引的元素替换,可以利用迭代器实现

new_values = iter(list('abcdefgh'))
df_ex.rename(index=lambda x:next(new_values),level=2)
BigCD
Smallcdcd
Othercatdogcatdogcatdogcatdog
UpperLowerExtra
Aaa36-9-6-6-209
b-5-33-8-3-258
bc-44-107-466
d-99-685-2-9-8
Bae0-91-629-7-9
f-9-5-4-3-186-5
bg01-8-8-20-6-3
h259-95-631

若想要对某个位置的元素进行修改,在单层索引时容易实现,即先取出索引的values属性,再给对得到的列表进行修改,最后再对index对象重新赋值。
但是如果是多级索引的话就有些麻烦,一个解决的方案是先把某一层索引临时转为表的元素,然后再进行修改,最后重新设定为索引,下面一节将介绍这些操作。
另外一个需要介绍的函数是map,它是定义在Index上的方法,与前面rename方法中层的函数式用法是类似的,只不过它传入的不是层的标量值,而是直接传入索引的元组,这为用户进行跨层的修改提供了遍历。
例如,可以等价地写出上面的字符串转大写的操作:

# df_ex.rename_axis(index=lambda x:str.upper(x),columns=lambda x:str.upper(x))
df_temp = df_ex.copy()
new_index =df_temp.index.map(lambda x:(x[0],x[1],str.upper(x[2])))
df_temp.index = new_index
df_temp.head()
BigCD
Smallcdcd
Othercatdogcatdogcatdogcatdog
UpperLowerExtra
AaALPHA36-9-6-6-209
BETA-5-33-8-3-258
bALPHA-44-107-466
BETA-99-685-2-9-8
BaALPHA0-91-629-7-9

关于map的另一个使用方法是对多级索引的压缩,后续操作也会需要

df_temp = df_ex.copy()
new_index = df_temp.index.map(lambda x:(x[0]+'-'+x[1]+'-'+x[2]))
# 将三个层的索引用“-”连接起来
df_temp.index=new_index
df_temp.head()# 单层索引
BigCD
Smallcdcd
Othercatdogcatdogcatdogcatdog
A-a-alpha36-9-6-6-209
A-a-beta-5-33-8-3-258
A-b-alpha-44-107-466
A-b-beta-99-685-2-9-8
B-a-alpha0-91-629-7-9

同时,也可以反向地展开:

new_idx = df_temp.index.map(lambda x:tuple(x.split('-')))
# 用split将其元组分割
df_temp.index=new_idx
df_temp.head()
# 三层索引
BigCD
Smallcdcd
Othercatdogcatdogcatdogcatdog
Aaalpha36-9-6-6-209
beta-5-33-8-3-258
balpha-44-107-466
beta-99-685-2-9-8
Baalpha0-91-629-7-9

3. 索引的设置与重置

为了说明本节的函数,下面构造一个新表:

df_new = pd.DataFrame({'A':list('aacd'), 'B':list('PQRT'), 'C':[1,2,3,4]})
df_new
ABC
0aP1
1aQ2
2cR3
3dT4

索引的设置可以使用set_index完成,这里的主要参数是append,表示是否来保留原来的索引,直接把新设定的添加到原索引的内层:

df_new.set_index('A')
BC
A
aP1
aQ2
cR3
dT4
df_new.set_index('A',append=True) # 保留原来的索引
BC
A
0aP1
1aQ2
2cR3
3dT4

可以同时指定多个列作为索引:

df_new.set_index(['A','B'])
C
AB
aP1
Q2
cR3
dT4

如果想要添加索引的列没有出现其中,可直接在参数中传入相应的Series:

my_index = pd.Series(list('WXYZ'),name='D')
df_new = df_new.set_index(['A',my_index])
df_new
# 相当于新增了一列索引,可根据实际情况进行调整
BC
AD
aWP1
XQ2
cYR3
dZT4

reset_index是set_index的逆函数,其主要参数是drop,表示是否要把去掉的索引层丢弃,而不是添加到列中

df_new.reset_index(['D'])
# 默认是不丢弃,添加到列中
DBC
A
aWP1
aXQ2
cYR3
dZT4
df_new.reset_index(['D'], drop=True)
# 丢弃D
BC
A
aP1
aQ2
cR3
dT4

如果重置了所有的索引,那么pandas会直接重新生成一个默认索引:

df_new.reset_index()
# 重置索引
ADBC
0aWP1
1aXQ2
2cYR3
3dZT4

4. 索引的变形

在某些场合下,需要对索引做一些扩充或者剔除,更具体地要求是给定一个新的索引,把原表中相应的索引对应元素填充到新索引构成的表中。

# 例如,下面的表中给出了员工信息,需要重新制作一张新的表
# 要求增加一名员工的同时去掉身高列并增加性别列
df_reindex = pd.DataFrame({"Weight":[60,70,80], "Height":[176,180,179]}, index=['1001','1003','1002'])
df_reindex
WeightHeight
100160176
100370180
100280179
df_reindex.reindex(index=['1001','1003','1002','1004'],columns=['Weight','Gender'])
WeightGender
100160.0NaN
100370.0NaN
100280.0NaN
1004NaNNaN

这种需求常出现在时间序列索引的时间点填充以及ID编号的扩充。

另外,需要注意的是原来表中的数据和新表中会根据索引自动对齐,例如原先的1002号位置在1003号之后,而新表中相反,那么reindex中会根据元素对齐,与位置无关。
还有一个与reindex功能类似的函数是reindex_like,其功能是仿照传入的表的索引来进行被调用表索引的变形。

# 例如,现在以及存在一张表具备了目标索引的条件,那么上述功能可以如下等价地写出
df_existed = pd.DataFrame(index=['1001','1002','1003','1004'],columns=['Weight','Gender'])
df_reindex.reindex_like(df_existed)
WeightGender
100160.0NaN
100280.0NaN
100370.0NaN
1004NaNNaN

四、索引运算

1. 集合的运算法则

经常会有一种利用集合运算来取出符合条件行的需求,例如有两张表A和B,它们的索引都是员工编号,现在需要筛选出两表索引交集的所有员工信息,此时通过Index上的运算操作就很容易实现。

S A . i n t e r s e c t i o n ( S B ) = S A ∩ S B ⇔ { x ∣ x ∈ S A   a n d   x ∈ S B } \rm S_A.intersection(S_B) = \rm S_A \cap S_B \Leftrightarrow \rm \{x|x\in S_A\, and\, x\in S_B\} SA.intersection(SB)=SASB{xxSAandxSB}
S A . u n i o n ( S B ) = S A ∪ S B ⇔ { x ∣ x ∈ S A   o r   x ∈ S B } \rm S_A.union(S_B) = \rm S_A \cup S_B \Leftrightarrow \rm \{x|x\in S_A\, or\, x\in S_B\} SA.union(SB)=SASB{xxSAorxSB}
S A . d i f f e r e n c e ( S B ) = S A − S B ⇔ { x ∣ x ∈ S A   a n d   x ∉ S B } \rm S_A.difference(S_B) = \rm S_A - S_B \Leftrightarrow \rm \{x|x\in S_A\, and\, x\notin S_B\} SA.difference(SB)=SASB{xxSAandx/SB}
S A . s y m m e t r i c _ d i f f e r e n c e ( S B ) = S A △ S B ⇔ { x ∣ x ∈ S A ∪ S B − S A ∩ S B } \rm S_A.symmetric\_difference(S_B) = \rm S_A\triangle S_B\Leftrightarrow \rm \{x|x\in S_A\cup S_B - S_A\cap S_B\} SA.symmetric_difference(SB)=SASB{xxSASBSASB}

2. 一般的索引运算

由于集合的元素是互异的,但是索引中可能有相同的元素,先用unique去重后再进行运算。

# 构造两张最为简单的示例表进行演示:
df_set_1 = pd.DataFrame([[0,1],[1,2],[3,4]], index = pd.Index(['a','b','a'],name='id1'))
df_set_2 = pd.DataFrame([[4,5],[2,6],[7,1]],index = pd.Index(['b','b','c'],name='id2'))
id1,id2 = df_set_1.index.unique(), df_set_2.index.unique()
id1.intersection(id2)# 两表的交集

# 等价于
# id1 & id2
Index(['b'], dtype='object')
id1.union(id2)# 并集

# 等价于
# id1 | id2
Index(['a', 'b', 'c'], dtype='object')
id1.difference(id2)#差集

# 等价于
# (id1 ^ id2) & id1
Index(['a'], dtype='object')
# 对等差分:在A或B中的元素的集合,但不在它们的交点上
id1.symmetric_difference(id2)

# 等价于
# id1 ^ id2 # ^符号即对称差
Index(['a', 'c'], dtype='object')
# 对等差分示例:symmetric_difference()
A = {'a', 'b', 'c', 'd'}
B = {'c', 'd', 'e' }
C = {}
print(A.symmetric_difference(B))
print(B.symmetric_difference(A))
print(A.symmetric_difference(C))
print(B.symmetric_difference(C))
{'e', 'a', 'b'}
{'e', 'b', 'a'}
{'b', 'd', 'c', 'a'}
{'d', 'e', 'c'}

若两张表需要做集合运算的列并没有被设置索引:
①:先转成索引,运算后再恢复
②:利用isin函数

# 例如,在重置索引的第一张表中选出id列交集的所在行:
df_set_in_col_1 = df_set_1.reset_index()
df_set_in_col_2 = df_set_2.reset_index()
df_set_in_col_1
id101
0a01
1b12
2a34
df_set_in_col_2
id201
0b45
1b26
2c71
df_set_in_col_1[df_set_in_col_1.id1.isin(df_set_in_col_2.id2)]
id101
1b12
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值