简介

Pandas提供了高性能,易于使用的数据结构和数据分析工具。Pandas用于广泛的领域,包括金融,经济,统计,分析等学术和商业领域。
官方网站:http://pandas.pydata.org/

安装

pip 安装:

pip install pandas -i https://mirrors.aliyun.com/pypi/simple/

导入模块

导入后取个别名方便调用:

import pandas as pd

数据结构

Pandas处理以下三种数据结构:

  1. 系列 Series
  2. 数据帧 DataFrame
  3. 面板 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()

pandas_第1张图片

统计中位数

上面的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()

图形如下:
pandas_第2张图片

也可以这样只画一根线:

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()

展示图形如下:
pandas_第3张图片

这里还有一种调用方法,效果是一样的。顺便上面的图有点小,再加个参数设置下尺寸:

df.plot(kind='bar', figsize=(20, 5))

展示图形如下:
pandas_第4张图片

累加的柱状图
如果想要查看每月总的事件数量,可以这样:

df.plot(kind='bar', stacked=True)  # 累加的柱状图

展示图形如下:
pandas_第5张图片

直方图

直方图(Histogram),又称质量分布图,是一种统计报告图,由一系列高度不等的纵向条纹或线段表示数据分布的情况。 一般用横轴表示数据类型,纵轴表示分布情况。

生成4组100个随机数样本:

df = pd.DataFrame(np.random.rand(100, 4), columns=('A', 'B', 'C', 'D'))
df.hist(figsize=(15, 10))

生成图形如下:
pandas_第6张图片

这里的随机样本还是比较少的,如果样本足够多,生成的图形会更加趋于平坦:

df = pd.DataFrame(np.random.rand(10000, 4), columns=('A', 'B', 'C', 'D'))
df.hist(figsize=(15, 10))

增加样本数量后的图形:
pandas_第7张图片

密度图

绘制密度图,需要一个额外的库,所以需要安装一下:

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()

图形展示:
pandas_第8张图片

标准正态分布俗称高斯分布,正态分布是大自然中最常见的分布,标准正态分布就是期望为0,方差为1的正态分布。

要获得其他正态分布,可以先用乘法改变方差,再用加法调整期望值:

2.5 * np.random.randn(100) + 3