pandas模块中的数据结构是基于numpy模块构建而成的。pandas的出现,让python语言成为使用最广泛且最强大的数据分析语言。
pandas的卖点在于:针对表格文件的操作具有非常大的优势,尤其是数据量超过10万行的文件。
这样听起来比较空泛,更接地气的解释是:pandas模块可以帮助我们通过代码操作excel表格。
内含具备诸多功能的两大数据结构:Series、DataFrame,二者也都是基于numpy构建出来的。公司中使用频繁的是DataFrame,而Series是构成DataFrame的基础,即一个DataFrame数据可能由多个Series数据构成。
继承了时间序列功能。
提供了丰富的数学运算和操作(基于Numpy模块)。
可以灵活处理各种数据。
在python开发环境下:
pip3 install pandas
在anaconda下:
conda install pandas
anaconda已经自动帮我们下载好了数据分析相关的模块,所以pandas模块我们无需再下载。
import pandas import pandas as pd # 习惯于起个别名为pd
数据分析三剑客模块由于使用频率很高,所以在很多ipynb文件的开头都会提前导入。
import numpy as np import pandas as pd
Series是一种类似于一位数组对象,由数据和相关的标签(索引)组成。
pd.Series([数据1,数据2...]) eg: pd.Series([1,2,3,4]) # 类似于numpy中的array([1,2,3,4])
可以看到,Series内部的数据都列在右侧,左侧则是程序自动生成的序号,序号在series被称作行索引。并且,Series还非常贴心地显示出数据是什么类型的。
pd.Series([数据1,数据2],index=['索引1','索引2'...]) eg: pd.Series([5,6,7,8],index=['a','b','c','d'])
通过关键字index,我们可以自己指定series使用什么字符作为行索引。为了便于区分,我们管数字序号叫行索引,字母或其他字符序号叫行标签。
pd.Series({'键1':值1,'键2':值2...}) eg: pd.Series({'a':1,'b':2})
可以将键值对的键作为行索引。
pd.Series(数据,index=['索引1','索引2'...]) eg: pd.Series(0,index=['a','b','c'])
创建值相同的Series数据,或许可以类比链式赋值?
我们来看这样一个现象:
# 范例 pd.Series([1,2,3,4.0]) """ 0 1.0 1 2.0 2 3.0 3 4.0 dtype: float64 """
和numpy的数组一样,Series内部元素的数据类型都要保持一致,究其原因自然是因为pandas的底层模块就有numpy。
还是通过案例出发。
st = {"joe":18,"simon":19,"frank":20,"jerry":21} obj = pd.Series(st) # 定义新索引 new_st = {'joe','simon','jerry','eddie'} # 修改原索引 obj1 = pd.Series(st,index=new_st) # 查看数据变化 obj1
运行完代码后,frank的数据被票没了,而eddie的数据却是三个字母。
新索引(标签)中没有frank,所以frank的数据被剔除,而新加入的eddie空有索引却没有值,所以就写作NaN。
NaN可理解为“not a number”,即数据缺失。
NaN属于浮点型。所以只要数据中出现NaN,数据类型就会变成浮点型。
isnull() # 判断数据是否为空,返回布尔数组 notnull() # 判断数据是否不为空(即上一条代码的结果取反),返回布尔数组 fillna() # 填充缺失数据 dropna() # 删除缺失数据
刚才填充上了数据,现在我们再来看看Series内的元素。
没有变化?可是刚才不是已经填充数据了吗?
其实是因为在修改后,内存空间中产生了一个新的Series数据来存放修改的数据,而原来的Series数据没有进行改动。
如果执行代码之后有反馈结果,说明原数据没有变。
eg: obj1.fillna(114514)
如果执行代码之后没有反馈结果,说明原数据改变。
eg: obj1.fillna(666,inplace=True)
inplace=True该参数很多方法都有,意思就是直接改变原数据。
有这样一个Series数据:
se1 = pd.Series([321312,123,324,5654,645])
现在需要筛选出大于800的数字。
我们先来试试numpy里的方法好不好使。
se1 > 800 # 即numpy数组中的逻辑索引
果然验证了pandas运用了numpy作为底层模块,numpy的比较运算符可以直接拿来用!
我们再来一道派生题。
mask = pd.Series([True,False,False,True,False]) price = pd.Series([321312,123,324,5654,645]) price[mask] # 将对应为True的元素保留 """ 0 321312 3 5654 """
price | mask """ 0 True 1 True 2 True 3 True 4 True """
|在正则表达式里学过,是或的意思。
在这道例题中,只要有一方值为True,那么结果就为True。
现在要求价格大于200且小于900的数据,结合之前的语句,可否写出来?
(price > 200) & (price < 900) """ 0 False 1 False 2 True 3 False 4 True """
这样看起来没什么难度,但需要注意一点:用&符号链接的条件都必须要加括号。
想要打印结果,那只要price用中括号将这个语句括起来就可以了。
price[(price > 200) & (price < 900)] """ 2 324 4 645 """
现在我们可以兑布尔值索引下定义了:本质是按照对应关系筛选出True对应的数据。
之前已经说过了行索引和行标签的区别,Series没有列索引的概念,因为Series数据只有一列。
数据名[行索引] eg: price[0] # 321312
这次换一个Series数据取值。
s1 = pd.Series({'a':'joe','b':'simon','c':'frank','d':'jerry'})
即便是使用键值对创建的Series数据也依然可以用行索引取值:
s1[0] # 'joe'
同样的,使用行标签也可以取到值。
数据名['行标签'] eg: s1['a'] # 'joe'
但是行标签取值有时可能出问题:
s2 = pd.Series({1:'joe',2:'simon',3:'frank',4:'jerry'}) # 获取joe s2[0]
直接报错。
因为行标签和行索引冲突了。
如果行标签也是数字的话,那么电脑在进行取值时按照的是行标签进行取值。但是这题中的行标签并没有0,所以计算机取不到值,于是就报错了(如果取值取的是1和更大的数字,则按照行标签也就是键值对中的键正常取值)。
所以一般推荐用键值对创建的Series数据用英文字符做行标签,以免发生混淆。
但如果非要头铁行标签强行用数字,那么在用行索引取值时可以使用iloc关键字取值,用行标签取值就用loc关键字。
用数字作为行标签的数据名.iloc[行索引] # 按行索引取值 eg: s2.iloc[1] # 'simon' 用数字作为行标签的数据名.loc['行标签'] # 按行标签取值 eg: s2.loc[1] # 'joe'
实在是吃不准数据使用的是行索引还是行标签,我们可以借助电脑来分辨。
数据名.index # 查看Series数据的索引值 eg: s2.index
所以下次遇到判断不了究竟该用行索引还是行标签的情况可以先用index分辨出索引值来,然后再取值即可。
其实案例看到这里大致也能明白了,基于notebook使用的ipynb文件和py文件存在区别:
notebook可以把每一个程序的运行结果保存在文档里,无需反复执行;而py文件则只能保存代码,想要获取结果必须重新运行一遍。
这是由两种文件的使用者和应用场景决定的。
py文件大多由python程序员编写。他们的目的是写出完整的程序,然后保证程序可以在不同环境下运作,注重的是程序最后达成的结果。
而ipynb文件则多由数据分析师、金融操盘手使用。他们的目的是经由程序获取数据,从数据中分析结果提炼规律,注重的是程序运行过程中的反馈。
两者没有优劣之分,只有适合的场合及使用者有区别。