简介
Pandas提供了高性能,易于使用的数据结构和数据分析工具。Pandas用于广泛的领域,包括金融,经济,统计,分析等学术和商业领域。
官方网站:http://pandas.pydata.org/
安装
pip 安装:
pip install pandas -i https://mirrors.aliyun.com/pypi/simple/
导入模块
导入后取个别名方便调用:
import pandas as pd
数据结构
Pandas处理以下三种数据结构:
- 系列 Series
- 数据帧 DataFrame
- 面板 Panel
Series是1维的。DataFrame是2维的,是Series的容器。Panel是3维的,是DataFrame的容器。
这些数据结构构建在Numpy数组之上,这意味着它们很快。
一般用的最多的就是表格,二维数据,下面只讲了DataFrame。
准备基础数据
这里有随机函数生成了一个学生成绩的数据表:
import random
data = [(i+1,
random.choice(("一班", "二班", "三班", "四班", "五班")),
random.choice(("男", "女")),
random.randint(70, 100),
random.randint(70, 100),
random.randint(70, 100),
random.randint(70, 100),
random.randint(70, 100),
random.randint(70, 100)) for i in range(100)]
DataFrame
接下来就是使用上面的这份数据进行演示
生成表格
生成的data是一个二维数组的结果,可以方便的生成表格:
df = pd.DataFrame(data, columns=["序号", "班级", "性别", "语文", "数学", "英语", "物理", "化学", "生物"])
这里的参数指定了数据源以及每一行的标题。
如果数据源是csv文件,也提供了专门的方法:
csv_data = pd.read_csv('source.csv')
推荐使用pandas处理csv文件。
这里再专注一下生成的df的类型:
print(type(df))
# pandas.core.frame.DataFrame
这个数据类型 DataFrame 是这个模块的核心。
显示数据
选中 前/后 几条:
df.head()
df.tail()
直接显示df会比较多,默认是5条,可以通过参数指定具体的数量
列名:
df.columns
# Index(['序号', '班级', '性别', '语文', '数学', '英语', '物理', '化学', '生物'], dtype='object')
索引:
df.index
# RangeIndex(start=0, stop=100, step=1)
筛选数据
筛选
筛选数学成绩:
(df.数学 > 88).head()
"""
0 False
1 False
2 False
3 True
4 False
Name: 数学, dtype: bool
"""
这里给的只是布尔值,一般是期望获得一个新的表格,表格里的数据是所有符合条件的数据:
df[df.数学 > 88]
上面的表达式只是计算获得一个新的表格,但是并没有对这个表格进行赋值。df原本的内容是没有改变的,而这个新的表格由于没有引用,之后也不能再使用。如果需要再用,那就把它传给一个变量。或者赋值给df,更新df里的内容:
df = df[df.数学 > 88]
复杂的筛选
也是可以对多个条件进行筛选的,下面是一个例子:
df[(df.数学 > 88) & (df.语文 < 80) & (df.英语 > 95)]
排序
排序,默认是升序:
df.sort_values(["数学"])
降序的话,通过参数指定:
df.sort_values(["数学"], ascending=False)
另外,还可以对多列进行排序,并且没一列指定是升序或降序:
# 第一个降序,第二个升序
df.sort_values(["数学", "英语"], ascending=[False, True])
索引
按照索引定位:
df.loc[0]
"""
序号 1
班级 五班
性别 女
语文 100
数学 72
英语 96
物理 98
化学 76
生物 73
Name: 0, dtype: object
"""
指定索引
之前没有指定索引,所以使用的是默认生成的索引,也就是0开始的整数。
下面的示例中,会生成一组新的数据,并指定数据的索引:
source = {
"name": ["Adam", "Barry", "Clark", "Diana"],
"title": ["Adam Warlock", "The Flash", "Superman", "Wonder Woman"],
"universe": ["Marvel", "DC", "DC", "DC"],
"gender": ["Male", "Male", "Male", "Female"],
}
df2 = pd.DataFrame(source, index=("one", "two", "three", "four")) # 指定索引
现在这组数据的索引就是index指定的字符串了。
使用索引访问
现在依然是使用索引来访问,就不能再用数字了:
df2.loc["one"] # 必须是索引
"""
name Adam
title Adam Warlock
universe Marvel
gender Male
Name: one, dtype: object
"""
使用行号访问
不过有另一个方法,提供使用数字的访问方式:
df2.iloc[0] # iloc 才是真正的行号
"""
name Adam
title Adam Warlock
universe Marvel
gender Male
Name: one, dtype: object
"""
混用
还有一个方法,可以混用上面的任意一种,不过并不推荐使用。使用后也会输出一条 Warning 信息,建议使用 loc 或 iloc:
df2.ix["one"] # ix 可以两种混用,但是不推荐使用,会出现Warning
高级用法
访问数据
可以使用点的形式访问,类似属性。也可以使用方括号的形式访问,类似字典。
下面的两条效果一样,都是选中数学这一列:
df.数学
df["数学"]
选择多列
如果要选择多列,就要用方括号了:
df[["数学", "英语"]] # 内嵌的需要是数组,不能是元组
选择多行
相当于是切片操作:
df.loc[:2] # 选择从开头到下标2的那条,一共3条
更负责的选择
就是上面的方法的联合使用,对行切片并指定需要的列:
df.loc[:2, "班级"] # 只要班级这列
df.loc[:2, ["语文", "数学", "英语"]] # 指定更多列
df.loc[[1, 2], ["语文", "数学", "英语"]] # 指定需要那几个索引的行
条件筛选
可以使用条件筛选,然后依然是指定加上指定需要的列:
# 条件筛选后输出指定列
df.loc[df.班级 == "一班", ["班级", "性别", "生物"]]
转为数组
使用 values 可以把表格的数组再转为数组:
df.head().values # 二维数组
"""
Out[25]:
array([[1, '五班', '女', 100, 72, 96, 98, 76, 73],
[2, '一班', '女', 92, 84, 93, 70, 92, 99],
[3, '二班', '女', 81, 72, 76, 80, 98, 77],
[4, '四班', '男', 99, 89, 89, 86, 85, 86],
[5, '五班', '女', 93, 85, 82, 85, 92, 72]], dtype=object)
"""
这是一个2维数组,如果输出的是某一列,就是1维数组了:
df.数学.values # 一维数组
"""
array([ 90, 84, 83, 74, 71, 83, 83, 71, 73, 92, 84, 90, 93,
98, 91, 81, 86, 84, 83, 84, 97, 83, 73, 96, 81, 98,
84, 83, 86, 85, 78, 94, 81, 97, 98, 96, 86, 78, 95,
83, 91, 75, 82, 96, 75, 87, 81, 82, 96, 79, 90, 92,
80, 100, 77, 81, 79, 80, 75, 87, 71, 97, 97, 98, 98,
88, 100, 80, 87, 84, 72, 74, 98, 87, 93, 72, 72, 81,
78, 75, 84, 77, 89, 75, 82, 81, 99, 93, 94, 84, 75,
78, 92, 88, 100, 70, 99, 92, 81, 76], dtype=int64)
"""
维度还是根据数据的类型决定的,这里已经是Series类型了:
type(df.数学)
# pandas.core.series.Series
统计
使用 value_counts 可以统计记录的数量:
df.班级.value_counts() # 统计每个班级的记录数
"""
二班 26
五班 26
一班 19
四班 18
三班 11
Name: 班级, dtype: int64
"""
统计语文成绩
假设90分以上是优秀,就来看看优秀的数据是怎么样的。下面这样直接对成绩进行统计效果会很差,因为分数的分布比较多,我们需要统计其中的一段分布,比如这里的90分以上:
df.语文.value_counts()
可以在统计之前做筛选,然后再看看各个班级语文优秀的分布:
df[df.语文 >90].班级.value_counts()
"""
二班 12
四班 8
五班 7
一班 6
三班 4
Name: 班级, dtype: int64
"""
这里做的相当于是聚类,之后讲map函数的时候,也能有类似的效果。先对值进行计算,用返回的新值生成一个新的列,然后再对新列进行统计也是很方便的。
聚合计算总数
简单的聚合,pandas自带的方法就可以实现:
df[df.语文 >90]["语文"].count()
# 37
更多聚类运算就不一一列举了。
复杂的聚合计算可以引入Numpy,这里的DataFrame就是一个二维数组,这个后面会用到。
进阶操作
pandas中的dataframe的操作,很大一部分是和numpy中的二维数组的操作是近似的。
map
使用map和自定义函数,可以根据原有的数据,生成新的列:
def level(score):
if score >= 90:
return "优"
elif score >= 80:
return "良"
elif score >= 70:
return "中"
else:
return "差"
# 根据原有的数据,使用map和自定义函数,生成新的列
df["数学等级"] = df.数学.map(level)
df[["数学", "数学等级"]]
applymap
这个函数和上面的map函数有点像,不过是一次对每一个(每一格)数据进行操作。这里做一个简单的例子,把所有的数据,也就是字符串,都用竖线包起来:
df.applymap(lambda x: "| " + str(x) + " |")
apply
这个函数可以按列(默认)或者按行来操作数据:
# df.apply把每一行,或者每一列的数据传值给x
# 最终的返回值就是第一个参数func的返回值
df.apply(lambda x: x.数学 + 0.5, axis=1).head()
"""
0 90.5
1 84.5
2 83.5
3 74.5
4 71.5
dtype: float64
"""
这个例子也不是太好,简单理解一下。匿名函数中x是每一行的数据,后面只取出了 x.数学 这一个值,所以最后输出的就只有数学的值了。
聚合计算
就是计算各种总和、最大、最小、平均值这类,计算使用numpy来实现。下面来计算一个所有人的平均分。因为取平均只能对数值进行操作,所以需要把对应的列筛选出来才能进行计算:
df[["语文", "数学", "英语"]].apply(np.average) # axis默认是0,不写了
"""
语文 85.52
数学 84.90
英语 84.93
dtype: float64
"""
这里只是简单的聚合计算,pandas就有方法可以实现。不过计算方面还是Numpy更强大,需要时可以像这样引入Numpy进行复杂的运算。
通过多列数据计算生成新列
下面的例子把物理、化学、生物的成绩综合起来计算出一个综合分:
df["综合"] = df.apply(lambda x: round((x.物理+x.化学+x.生物) / 3, 1), axis=1)
这次把计算的结果赋值给了 df["综合"],原来的df表格中就多了一个新的列,并且记录了这里计算出的结果。
转置
转置可以把行变成列,列变成行。另外原本每行的索引(index)转置后就变成了新的表格的列名(columns),列名则变为索引。
下面单独生成一个小数据,查看转置的效果:
df = pd.DataFrame([[1, 3, 5], [2, 4, 6]], index=("奇数", "偶数"))
df.T # df的转置
删除
使用 drop 可以进行删除的操作,下面是一个删除整列的例子,把上面生成的新列删除掉:
df = df.drop("综合", axis=1) # 重新赋值给df
注意这里的参数 axis=1,是指定按列进行操作。
另外这里也用了赋值语句,道理还是要使用操作后的新表格更新原来的df表格。
观察处理数据
在获取到一组数据后,总是需要大致的看一下这些数据的情况。已经学过 head 和 tail 可以查看最前面和最后的几个数据。另外还有一些好用的函数可以帮助我们对数据有一个大致的了解。
info
info 函数可以获取到所有数据的结构,相当于数据库的表结构:
df.info()
"""
RangeIndex: 100 entries, 0 to 99
Data columns (total 9 columns):
序号 100 non-null int64
班级 100 non-null object
性别 100 non-null object
语文 100 non-null int64
数学 100 non-null int64
英语 100 non-null int64
物理 100 non-null int64
化学 100 non-null int64
生物 100 non-null int64
dtypes: int64(7), object(2)
memory usage: 7.1+ KB
"""
describe
describe 函数可以自动找到那些数值型的数据,并且计算出各种统计值:
df.describe()
统计中位数
上面的describe输出的各种统计中,就包括中位数,如果要单独获取到每个数值,可以这样:
df.median()
"""
序号 50.5
语文 85.0
数学 85.0
英语 85.0
物理 84.5
化学 82.0
生物 86.0
dtype: float64
"""
空值统计
很多时候,收集到的数据并不会很完整,这里面就会有很多的空值。所以在使用之前,空值处理是很重要的。首先要进行统计空值,对空值的情况要有一个大概的了解。
先制造一些空值:
# 制造几个空值
df.iloc[2:8, 5] = None
查看空值:
df.isnull().head(10)
这个方法只是把原本显示的内容,替换为True或False,以表示该单元格是否为空值。数据很大的时候并不好用。
空值统计:
df.isnull().sum()
"""
序号 0
班级 0
性别 0
语文 0
数学 0
英语 6
物理 0
化学 0
生物 0
dtype: int64
"""
再调用一下sum方法,就把空值的数量统计出来了。
填充空值
对于空值,可以给这些空值填入一些合适的值。下面就是给这些空值全部填入0:
df.fillna(0).head(10)
上面的操作是给所有的单元格都进行填充。如果多个列都有空值,但是不想全部都填入0,可以只对部分数据进行填充。用好之前的数据筛选的方法就好了,比如只填充英语的空值:
df.英语.fillna(0).head(10)
填充并更新
上面这些操作并不会改变原本的表格。inplace参数可以直接更新到源数据,下面直接对英语的空值填充中位数,并且更新源数据:
# 默认返回新的值,不会修改原来的值
# 使用inplace参数,没有返回值,直接修改源数据了
df.英语.fillna(df.英语.median(), inplace=True)
中位数
对空值填充,需要根据情况选择填充合适的值,比如:零值,平均值、中位数。
中位数和平均值的差别,一般情况下这2个值会比较接近。但是如果数据中有个别极大或者级小的值,这些值就会对平均值的结果产生很大的影响,这些值可以被称作噪声。而中位数则可以在一定程度上排除一些噪声的干扰。所以具体要不要进行空值填充,具体填充什么样的值,还要具体情况具体分析。
绘图
绘图有专门的库matplotlib,一般用这个,功能强大。不过pandas本身自带绘图。
在 jupyter notebook 中绘图
需要运行下面这句,就可以直接把图画出来了:
%matplotlib inline
线性图
使用numpy生成随机数,然后用cumsum方法,产生一个累加的结果,这样可以产生一组不断上升的数据。
cumsum方法,也有一个效果相同的同名函数,类似于这个方法的方法表达式。就是数据是作为函数的第一个参数,还是作为方法的接收者。
df = pd.DataFrame(np.random.rand(100, 4).cumsum(0), columns=('A', 'B', 'C', 'D'))
df.plot()
也可以这样只画一根线:
df.A.plot()
cumsum 说明
因为这里产生了4组数据,是一个二维数组。cumsum里的参数指定累加的维度,这个参数需要是小于维度的一个正整数或0。对于二维数组,可选值就是0,或1。我们希望是得到4根累加的线,这里用了0。不好理解的话,可以看下面的例子:
l1 = np.array([i+1 for i in range(9)]).reshape(3, 3)
"""
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
"""
np.cumsum(l1) # 默认值是 None,按照一维数据累加了
# array([ 1, 3, 6, 10, 15, 21, 28, 36, 45], dtype=int32)
np.cumsum(l1, 0) # 纵向的累加
"""
array([[ 1, 2, 3],
[ 5, 7, 9],
[12, 15, 18]], dtype=int32)
"""
np.cumsum(l1, 1) # 横向的累加
"""
array([[ 1, 3, 6],
[ 4, 9, 15],
[ 7, 15, 24]], dtype=int32)
"""
回到上面生成的数据,这里生成了4组100个数据,需要纵向的累加。
柱状图
首先,还是生成数据。假设有4个人,统计每人每月处理的事件数量(随机生成1到100的整数),如下:
case_num = np.random.randint(1, 100, (12, 4))
df = pd.DataFrame(case_num,
columns=("Adam", "Bob", "Clark", "Dan"),
index=('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
df.plot.bar()
这里还有一种调用方法,效果是一样的。顺便上面的图有点小,再加个参数设置下尺寸:
df.plot(kind='bar', figsize=(20, 5))
累加的柱状图
如果想要查看每月总的事件数量,可以这样:
df.plot(kind='bar', stacked=True) # 累加的柱状图
直方图
直方图(Histogram),又称质量分布图,是一种统计报告图,由一系列高度不等的纵向条纹或线段表示数据分布的情况。 一般用横轴表示数据类型,纵轴表示分布情况。
生成4组100个随机数样本:
df = pd.DataFrame(np.random.rand(100, 4), columns=('A', 'B', 'C', 'D'))
df.hist(figsize=(15, 10))
这里的随机样本还是比较少的,如果样本足够多,生成的图形会更加趋于平坦:
df = pd.DataFrame(np.random.rand(10000, 4), columns=('A', 'B', 'C', 'D'))
df.hist(figsize=(15, 10))
密度图
绘制密度图,需要一个额外的库,所以需要安装一下:
pip install scipy -i https://mirrors.aliyun.com/pypi/simple/
这个库比较大,几十兆,最好指定国内镜像源。
数据方法,这里生成一组标准正态分布的随机数据:
df = pd.DataFrame(np.random.randn(100, 4), columns=('A', 'B', 'C', 'D'))
df.plot.kde()
标准正态分布俗称高斯分布,正态分布是大自然中最常见的分布,标准正态分布就是期望为0,方差为1的正态分布。
要获得其他正态分布,可以先用乘法改变方差,再用加法调整期望值:
2.5 * np.random.randn(100) + 3