1. pandas的概述
pandas基于list,将各个series结合的更好,使数据更加结构化,利于观察。
2. Pandas的索引
2.1 索引对象
2.1.1 索引对象是什么
pandas的索引对象其实就是Index对象,可以理解理解为每一行的独特标识,我们可以通过该标识取得我们想要的数据。
通常来说,pandas自动生成的索引是一个连续的整形数字。
2.1.2 pandas自动生成的索引有什么弊端
我们已经知道了pandas自动生成的索引是连续整形数字,那么这会带来什么问题?
试想,索引代表的是什么意思呢?这里的索引仅仅能够帮助我们定位,但我们不知道该索引对应的数据到底是什么。通常在使用了Pandas的自动生成的索引后,每次修改删除操作都会伴随着重置索引reset_index(drop = True)。
所以,在实际工作过程中,我们会更愿意找数据集中单独一个id列作为我们的索引。
2.1.3 索引值的修改
通常我们在将索引改为我们需要的id列时,都会使用这样一条语句,比如data.index = data["id"]。
这样的修改是可行的,将data.index的引用变为了data["id"]的内存地址。
但是对index对象内的单个索引值进行修改,这是不可行的,如data.index[1] = "001"。这里的修改只能对整个index对象改引用。
字符串也是一个类似的例子,如string = "abcde",我们是不能够修改string[1] = “q”的,但是能够直接将string的引用修改,如在string = "abcde" 的操作后string = "fghij"。
2.2 取数操作
2.2.1 取出第四列的数据(category)
方法:
- data.category
- data.iloc[:,4]
- data.loc[:,"category"]
注意点:开发过程中尽量使用.loc的方式查询数据,可读性会更好
2.2.2 取出第四行的数据
方法:
- data[3:4]
- data.iloc[3:4,:]
错误写法:
- data[4]
错误原因:如果列字段名也是数字,data[4]容易造成歧义,而拥有符号" : "的写法,是一个完整的声明,python可以知道这是一个没有歧义的写法。
2.2.3 取出多列数据
方法:
- data.loc[:,["category", "investment_strategy", "investment_type"]]
- data.loc[:, "category" : "investment_type"]
注意点:
- 在第一个方法中,之所以里边又加了一个中括号,是由于loc底层的实现是一个迭代器,我们需要传一个列表过去迭代。另外,若不加中括号,由于有多个逗号,语法也会发生错误。
- 在第二个方法中,我们使用了符号" : "达到了相同效果,由此可以带冒号的完整声明返回的是一个list对象。
2.2.4 bool索引
数据完美的情况下
注意点:bool索引值在比较后通过判断bool值筛选我们需要的数据
如现在有一列字段"fund_symbol",我们需要筛选该列大于1310的数据,我们可以这样判断:
data.loc[:,"fund_symbol"] > 1310,这会返回一个含bool值的series,再根据这个series筛选出我们需要的数据即可,如:data[data.loc[:,"fund_symbol"] > 1310]
数据不完美的情况下
通常来说,我们的数据并不是完美的,比如该列其实是个str对象,所以直接比较是会报错的,如刚才的语句:data.loc[:,"fund_symbol"] > 1310。
首先,假设该字段只包含数字形式的字符串,那么我们可以用以下方法解决这个问题:
- data.loc[:,"fund_symbol"].astype(int) > 1310
- data.loc[:,"fund_symbol"].apply(lambda x: int(x))
另外,如果该字段并不只包含数字形式的字符串怎么办?我们可以用以下方式解决,将字符串根据我们的需要转换为可以使用的数字,在这里会转换为1310
- data.loc[:,"fund_symbol"].apply(lambda x: int(x) if x.isdigit() else 1310)
值得注意的是,在很多语言中支持的三元表达式的写法在这里并不被支持,如data.loc[:,"fund_symbol"].apply(lambda x: x.isdigit() ? int(x) : 1310)会报错
3. 多层索引(层次索引)
3.1 举例
多层索引如字面所述,使用多列进行索引,如下所示:
其中,活跃程度和性别是我们的多层索引
3.2 生成方式
通常,我们使用set_index()来生成我们的多层索引,如我们的原始数据为:
接着,使用data.set_index(["活跃程度","性别"]),就可以得到我们3.1中的数据结果了。
3.3 取数操作
在对含有层次索引的数据集取数据的时候,我们需要根据元组进行索引,因为层次索引的索引值是以元组为基本单位的。如:data.loc[("高活跃","男"),:],会取到如下数据
3.4 层次索引的意义
层次索引其实代表的是一种流转,如3.1中的结果所示,代表的是不同活跃程度的人群到不同性别的流转,如果将层次索引颠倒过来,就可以代表不同性别的群体到不同活跃程度的流转。
比如电商等行业,就会特别关注不同活跃程度的人群到不同地区的流转。
3.5 层次索引的聚合
在确认了流转的方向后,我们便可以根据这个方向,对数据进行聚合。比如,我们想知道高活跃,性别男的平均年龄,具体操作如图所示:
3.6 层次索引聚合和group by聚合的区别
在这个层次索引中,我们可以清晰看到流转的情况,但是如果使用group by来聚合数据,我们仅能看到聚合后的结果,对中间过程没有一个详细的认知。
4. series和dataframe在apply时的不同
4.1 使用apply时的粒度
使用apply时,如果是dataframe对象使用apply,那么会对dataframe中的每个series进行运算。
如果是series使用apply,那么会对series中的每一行(即每一个对象)进行运算。
4.2 错误使用示例
需求:对之前例子的结果进行聚合运算
基于刚才的temp = data.loc[:,"fund_symbol"].apply(lambda x: int(x) if x.isdigit() else 1310),我们获得了完整的仅含int对象的数据列,类型为series,如果我们打算对这个series进行聚合操作,会产生报错,如:temp.apply(lambda x: x.max() - x.min())。原因在于series对象在实现apply方法时,传入的是该列中的每一行的int对象(即例子中的x),而int对象并不包含max()等聚合方法,因此会报错。
4.3 解决方法
针对刚才的错误,我们可以发现是粒度引起的问题,如果要解决问题,这里需要将series对象重新转换为dataframe对象,再apply,此时就可以完成聚合操作了,如下:
temp = pd.DataFrame(temp.values)
temp.apply(lambda x: x.max() - x.min)
此时,这个x就代表的是一列数据,即series,我们可以正常调用聚合函数来计算结果。
4.4 apply中axis的问题
首先值得一提的是,axis=0指多行操作,axis=1指对多列操作。什么意思呢?继续使用刚才的案例,temp.apply(lambda x: x.max() - x.min)的完整形式应该是temp.apply(lambda x: x.max() - x.min, axis = 0),只不过axis = 0是默认的,所以我们并没有声明。
axis = 0和axis = 1时到底有何不同呢?
在这个案例中,
当axis = 0时,会对纵轴方向的数据进行处理。这里会获得该series的所有行,如500行(个)不同数字的数据,再对这些数据进行max()和min()聚合运算,最终求得的值为单独的一个确定值。
当axis = 1时,会对横轴方向的数据进行处理。这时候进行max()和min()聚合运算时,横向只有单独的一个x值,如果这时对x进行操作,会发生广播运算。如x.max()的操作,会将max()广播到每一个x。因此在聚合后x.max()-x.min()=0。运算完成后,我们会获得计算结果全为0(500个0)的一个series对象。
5. Series的小知识
5.1 pd.Series
temp = pd.Series()可以创建一个空的Series,且dtype为float64,这是一个很反常的事,不过在执行语句时,官方声明在未来版本本会将初始化的空Series类型改为object,这会更符合常理。
5.2 空Series的添加
5.2.1 正确写法
- temp.append(pd.Series([1]))
5.2.2 错误写法
- temp.append(1)
- temp.append("a")
- temp.append(pd.Series(1))
错误原因:
- Series对象的增加操作只能增加Series和DataFrame类型的对象,不支持其它对象
- 在第三个错误写法中,Series支持通过list对象转变为Series,而不支持单独的数字转变,这是Series在封装时的规定。
5.3 空Series在添加元素后的类型问题
在4.1中,我们知道了空Series对象的类型为float64,那么为什么依然能添加其它包含不同类型的数据的Series对象呢?原因在于所有类型的对象都有一个共同的超类祖宗Object(即父类或祖宗类),python可以根据这一特效进行类型转换,在Series新增数据,类型更改为Object,即满足了不同类型对象共存的问题。
6. 其他补充
在dataframe进行apply操作时,是针对每一列或每一行Series进行操作,但是如果我们需要操作每一个单元格,这时可以使用applymap()方法。